Browse Source

Terminal: added support for ansi escape colors, and added links to Node.js stack trace and jest test output. Also added cargo command in preferences.

pipelines/235045657
eidheim 5 years ago
parent
commit
32e03b40ad
  1. 2
      CMakeLists.txt
  2. 1
      README.md
  3. 1
      src/config.cpp
  4. 1
      src/config.hpp
  5. 3
      src/files.hpp
  6. 4
      src/project_build.cpp
  7. 2
      src/project_build.hpp
  8. 316
      src/terminal.cpp
  9. 3
      src/terminal.hpp
  10. 2
      tests/stubs/project.cpp
  11. 176
      tests/terminal_test.cpp

2
CMakeLists.txt

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 2.8.8) cmake_minimum_required (VERSION 2.8.8)
project(juci) project(juci)
set(JUCI_VERSION "1.6.0.5") set(JUCI_VERSION "1.6.0.6")
set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_NAME "jucipp")
set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim <eidheim@gmail.com>") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim <eidheim@gmail.com>")

1
README.md

@ -35,6 +35,7 @@ can use juCi++ through POSIX compatibility layers such as MSYS2.
* Find symbol through Ctags ([Universal Ctags](https://github.com/universal-ctags/ctags) is recommended) * Find symbol through Ctags ([Universal Ctags](https://github.com/universal-ctags/ctags) is recommended)
* Spell checking depending on file context * Spell checking depending on file context
* Run shell commands within juCi++ * Run shell commands within juCi++
* ANSI colors are supported. Enable for instance by setting the environment variables `CLICOLOR=1 CLICOLOR_FORCE=1` before starting juCi++. Colored diagnostics from clang is enabled through the flag `-fcolor-diagnostics`, and gcc uses the flag `-fdiagnostics-color`.
* Regex search and replace * Regex search and replace
* Smart paste, keys and indentation * Smart paste, keys and indentation
* Extend/shrink selection * Extend/shrink selection

1
src/config.cpp

@ -201,6 +201,7 @@ void Config::read(const boost::property_tree::ptree &cfg) {
project.clear_terminal_on_compile = cfg.get<bool>("project.clear_terminal_on_compile"); project.clear_terminal_on_compile = cfg.get<bool>("project.clear_terminal_on_compile");
project.ctags_command = cfg.get<std::string>("project.ctags_command"); project.ctags_command = cfg.get<std::string>("project.ctags_command");
project.grep_command = cfg.get<std::string>("project.grep_command"); project.grep_command = cfg.get<std::string>("project.grep_command");
project.cargo_command = cfg.get<std::string>("project.cargo_command");
project.python_command = cfg.get<std::string>("project.python_command"); project.python_command = cfg.get<std::string>("project.python_command");
project.markdown_command = cfg.get<std::string>("project.markdown_command"); project.markdown_command = cfg.get<std::string>("project.markdown_command");

1
src/config.hpp

@ -49,6 +49,7 @@ public:
bool clear_terminal_on_compile; bool clear_terminal_on_compile;
std::string ctags_command; std::string ctags_command;
std::string grep_command; std::string grep_command;
std::string cargo_command;
std::string python_command; std::string python_command;
std::string markdown_command; std::string markdown_command;
}; };

3
src/files.hpp

@ -75,7 +75,7 @@ const std::string default_config_file = R"RAW({
"debug_place_cursor_at_stop": false "debug_place_cursor_at_stop": false
}, },
"terminal": { "terminal": {
"history_size": 1000, "history_size": 10000,
"font_comment": "Use \"\" to use source.font with slightly smaller size", "font_comment": "Use \"\" to use source.font with slightly smaller size",
"font": "" "font": ""
}, },
@ -210,6 +210,7 @@ const std::string default_config_file = R"RAW({
#endif #endif
R"RAW( R"RAW(
"grep_command": "grep", "grep_command": "grep",
"cargo_command": "cargo",
"python_command": "PYTHONUNBUFFERED=1 python", "python_command": "PYTHONUNBUFFERED=1 python",
"markdown_command": "grip -b" "markdown_command": "grip -b"
}, },

4
src/project_build.cpp

@ -174,6 +174,10 @@ std::vector<boost::filesystem::path> Project::CompileCommandsBuild::get_exclude_
return exclude_paths; return exclude_paths;
} }
std::string Project::CargoBuild::get_compile_command() {
return Config::get().project.cargo_command + " build";
}
std::vector<boost::filesystem::path> Project::CargoBuild::get_exclude_paths() { std::vector<boost::filesystem::path> Project::CargoBuild::get_exclude_paths() {
auto exclude_paths = Project::Build::get_exclude_paths(); auto exclude_paths = Project::Build::get_exclude_paths();
exclude_paths.emplace_back(project_path / "target"); exclude_paths.emplace_back(project_path / "target");

2
src/project_build.hpp

@ -65,7 +65,7 @@ namespace Project {
boost::filesystem::path get_debug_path() override { return get_default_path(); } boost::filesystem::path get_debug_path() override { return get_default_path(); }
bool update_debug(bool force = false) override { return true; } bool update_debug(bool force = false) override { return true; }
std::string get_compile_command() override { return "cargo build"; } std::string get_compile_command() override;
boost::filesystem::path get_executable(const boost::filesystem::path &path) override { return get_debug_path() / project_path.filename(); } boost::filesystem::path get_executable(const boost::filesystem::path &path) override { return get_debug_path() / project_path.filename(); }
std::vector<boost::filesystem::path> get_exclude_paths() override; std::vector<boost::filesystem::path> get_exclude_paths() override;

316
src/terminal.cpp

@ -21,58 +21,178 @@ Terminal::Terminal() : Source::SearchView() {
link_tag = get_buffer()->create_tag(); link_tag = get_buffer()->create_tag();
link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE; link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE;
invisible_tag = get_buffer()->create_tag();
invisible_tag->property_invisible() = true;
red_tag = get_buffer()->create_tag();
green_tag = get_buffer()->create_tag();
yellow_tag = get_buffer()->create_tag();
blue_tag = get_buffer()->create_tag();
magenta_tag = get_buffer()->create_tag();
cyan_tag = get_buffer()->create_tag();
link_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::HAND1); link_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::HAND1);
default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM); default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM);
// Apply link tags class DetectPossibleLink {
get_buffer()->signal_insert().connect([this](const Gtk::TextIter &iter, const Glib::ustring &text, int /*bytes*/) { bool delimiter_found = false, dot_found = false, number_after_delimiter_and_dot_found = false;
std::string line_start;
size_t line_start_pos = 0;
bool delimiter_found = false;
bool dot_found = false;
bool number_found = false;
auto start = iter; public:
start.backward_chars(text.size()); bool operator()(char chr) {
int line_nr = start.get_line(); if(chr == '\n') {
auto all_found = delimiter_found && dot_found && number_after_delimiter_and_dot_found;
delimiter_found = dot_found = number_after_delimiter_and_dot_found = false;
return all_found;
}
else if(chr == '/' || chr == '\\')
delimiter_found = true;
else if(chr == '.')
dot_found = true;
else if(delimiter_found && dot_found && chr >= '0' && chr <= '9')
number_after_delimiter_and_dot_found = true;
return false;
}
};
class ParseAnsiEscapeSequence {
enum class State { none = 1, escaped, parameter_bytes, intermediate_bytes };
State state = State::none;
std::string parameters;
size_t length = 0;
public:
struct Sequence {
std::string arguments;
size_t length;
char command;
};
auto parse_text = [&](const std::string &text) { boost::optional<Sequence> operator()(char chr) {
if(chr == '\e') {
state = State::escaped;
parameters = {};
length = 1;
}
else if(state != State::none) {
++length;
if(chr == '[') {
if(state == State::escaped)
state = State::parameter_bytes;
else
state = State::none;
}
else if(chr >= 0x30 && chr <= 0x3f) {
if(state == State::parameter_bytes)
parameters += chr;
else
state = State::none;
}
else if(chr >= 0x20 && chr <= 0x2f) {
if(state == State::parameter_bytes)
state = State::intermediate_bytes;
else if(state != State::intermediate_bytes)
state = State::none;
}
else if(chr >= 0x40 && chr <= 0x7e) {
if(state == State::parameter_bytes || state == State::intermediate_bytes) {
state = State::none;
return Sequence{std::move(parameters), length, chr};
}
else
state = State::none;
}
else
state = State::none;
}
return {};
}
};
get_buffer()->signal_insert().connect([this, detect_possible_link = DetectPossibleLink(), parse_ansi_escape_sequence = ParseAnsiEscapeSequence(), last_color = -1, last_color_sequence_mark = std::shared_ptr<Source::Mark>()](const Gtk::TextIter &iter, const Glib::ustring &text_, int /*bytes*/) mutable {
boost::optional<Gtk::TextIter> start_of_text;
int line_nr_offset = 0;
auto get_line_nr = [&] {
if(!start_of_text) {
start_of_text = iter;
start_of_text->backward_chars(text_.size());
}
return start_of_text->get_line() + line_nr_offset;
};
const auto &text = text_.raw();
for(size_t i = 0; i < text.size(); ++i) { for(size_t i = 0; i < text.size(); ++i) {
if(text[i] == '\n') { if(detect_possible_link(text[i])) {
if(delimiter_found && dot_found && number_found) { auto start = get_buffer()->get_iter_at_line(get_line_nr());
if(auto link = !line_start.empty() auto end = start;
? find_link(line_start + text.substr(0, i)) if(!end.ends_line())
: find_link(text, line_start_pos, i - line_start_pos)) { end.forward_to_line_end();
auto link_start = get_buffer()->get_iter_at_line(line_nr); if(auto link = find_link(get_buffer()->get_text(start, end, false).raw())) { // Apply link tags
auto link_start = start;
if(link_start.has_tag(invisible_tag))
link_start.forward_visible_cursor_position();
auto link_end = link_start; auto link_end = link_start;
link_start.forward_chars(link->start_pos); link_start.forward_visible_cursor_positions(link->start_pos);
link_end.forward_chars(link->end_pos); link_end.forward_visible_cursor_positions(link->end_pos);
get_buffer()->apply_tag(link_tag, link_start, link_end); get_buffer()->apply_tag(link_tag, link_start, link_end);
} }
} }
line_start_pos = i + 1; if(auto sequence = parse_ansi_escape_sequence(text[i])) {
delimiter_found = false; auto end = iter;
dot_found = false; end.backward_chars(utf8_character_count(text, i + 1));
number_found = false; auto start = end;
++line_nr; start.backward_chars(sequence->length);
line_start.clear(); get_buffer()->apply_tag(invisible_tag, start, end);
if(sequence->command == 'm') {
int color = -1;
if(sequence->arguments.empty())
color = 0;
else {
size_t pos = 0;
size_t start_pos = pos;
while(true) {
pos = sequence->arguments.find(";", pos);
try {
auto code = std::stoi(sequence->arguments.substr(start_pos, pos != std::string::npos ? pos - start_pos : pos));
if(code == 39)
color = 0;
else if(code == 38) {
color = 0;
break; // Do not read next arguments
}
else if(code == 48 || code == 58)
break; // Do not read next arguments
else if(code == 0 || (code >= 30 && code <= 37))
color = code;
} }
else if(text[i] == '/' || text[i] == '\\') catch(...) {
delimiter_found = true; }
else if(text[i] == '.') if(pos == std::string::npos)
dot_found = true; break;
else if(text[i] >= '0' && text[i] <= '9') pos += 1;
number_found = true; start_pos = pos;
}
}
if(last_color >= 0) {
if(last_color == 31)
get_buffer()->apply_tag(red_tag, (*last_color_sequence_mark)->get_iter(), start);
else if(last_color == 32)
get_buffer()->apply_tag(green_tag, (*last_color_sequence_mark)->get_iter(), start);
else if(last_color == 33)
get_buffer()->apply_tag(yellow_tag, (*last_color_sequence_mark)->get_iter(), start);
else if(last_color == 34)
get_buffer()->apply_tag(blue_tag, (*last_color_sequence_mark)->get_iter(), start);
else if(last_color == 35)
get_buffer()->apply_tag(magenta_tag, (*last_color_sequence_mark)->get_iter(), start);
else if(last_color == 36)
get_buffer()->apply_tag(cyan_tag, (*last_color_sequence_mark)->get_iter(), start);
} }
};
if(!start.starts_line()) { if(color >= 0) {
auto end = start; last_color = color;
start = get_buffer()->get_iter_at_line(start.get_line()); last_color_sequence_mark = std::make_shared<Source::Mark>(end);
line_start = get_buffer()->get_text(start, end).raw(); }
parse_text(line_start); }
}
if(text[i] == '\n')
++line_nr_offset;
} }
parse_text(text.raw());
}); });
} }
@ -239,11 +359,13 @@ boost::optional<Terminal::Link> Terminal::find_link(const std::string &line, siz
const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages
"^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info "^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info
"^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc) "^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc)
"^ --> ([A-Z]:)?([^:]+):([0-9]+):([0-9]+)$|" // Rust "^ +--> ([A-Z]:)?([^:]+):([0-9]+):([0-9]+)$|" // Rust
"^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" // clang assert() "^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" // clang assert()
"^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" // gcc assert() "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" // gcc assert()
"^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$|" // g_assert (glib.h) "^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$|" // g_assert (glib.h)
"^([A-Z]:)?([\\/][^:]+):([0-9]+)$|" // Node.js "^([A-Z]:)?([\\/][^:]+):([0-9]+)$|" // Node.js
"^ at .*?\\(([A-Z]:)?([\\/][^:]+):([0-9]+):([0-9]+)\\)$|" // Node.js stack trace
"^ at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\)$|" // Node.js Jest
"^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$", // Python "^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$", // Python
std::regex::optimize); std::regex::optimize);
std::smatch sm; std::smatch sm;
@ -251,7 +373,11 @@ boost::optional<Terminal::Link> Terminal::find_link(const std::string &line, siz
line.cbegin() + (length == std::string::npos ? line.size() : std::min(pos + length, line.size())), line.cbegin() + (length == std::string::npos ? line.size() : std::min(pos + length, line.size())),
sm, link_regex)) { sm, link_regex)) {
for(size_t sub = 1; sub < link_regex.mark_count();) { for(size_t sub = 1; sub < link_regex.mark_count();) {
size_t subs = sub == 1 || sub == 4 + 3 + 3 + 1 ? 4 : 3; size_t subs = (sub == 1 ||
sub == 1 + 4 + 3 + 3 ||
sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 ||
sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 + 4) ?
4 : 3;
if(sm.length(sub + 1)) { if(sm.length(sub + 1)) {
auto start_pos = static_cast<int>(sm.position(sub + 1) - sm.length(sub)); auto start_pos = static_cast<int>(sm.position(sub + 1) - sm.length(sub));
auto end_pos = static_cast<int>(sm.position(sub + subs - 1) + sm.length(sub + subs - 1)); auto end_pos = static_cast<int>(sm.position(sub + subs - 1) + sm.length(sub + subs - 1));
@ -289,33 +415,7 @@ void Terminal::print(std::string message, bool bold) {
scroll_to_bottom(); scroll_to_bottom();
} }
#ifdef _WIN32
// Remove color codes
size_t pos = 0;
while((pos = message.find('\e', pos)) != std::string::npos) {
if((pos + 2) >= message.size())
break;
if(message[pos + 1] == '[') {
size_t end_pos = pos + 2;
bool color_code_found = false;
while(end_pos < message.size()) {
if((message[end_pos] >= '0' && message[end_pos] <= '9') || message[end_pos] == ';')
end_pos++;
else if(message[end_pos] == 'm') {
color_code_found = true;
break;
}
else
break;
}
if(color_code_found)
message.erase(pos, end_pos - pos + 1);
}
}
Glib::ustring umessage = std::move(message); Glib::ustring umessage = std::move(message);
#else
Glib::ustring umessage = std::move(message);
#endif
Glib::ustring::iterator iter; Glib::ustring::iterator iter;
while(!umessage.validate(iter)) { while(!umessage.validate(iter)) {
@ -329,11 +429,9 @@ void Terminal::print(std::string message, bool bold) {
else else
get_buffer()->insert(get_buffer()->end(), umessage); get_buffer()->insert(get_buffer()->end(), umessage);
if(get_buffer()->get_line_count() > Config::get().terminal.history_size) { auto excess_lines = get_buffer()->get_line_count() - Config::get().terminal.history_size;
int lines = get_buffer()->get_line_count() - Config::get().terminal.history_size; if(excess_lines > 0)
get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(excess_lines));
deleted_lines += static_cast<size_t>(lines);
}
} }
void Terminal::async_print(std::string message, bool bold) { void Terminal::async_print(std::string message, bool bold) {
@ -345,6 +443,52 @@ void Terminal::async_print(std::string message, bool bold) {
void Terminal::configure() { void Terminal::configure() {
link_tag->property_foreground_rgba() = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK); link_tag->property_foreground_rgba() = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK);
auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL);
auto light_theme = (normal_color.get_red() + normal_color.get_green() + normal_color.get_blue()) / 3 < 0.5;
Gdk::RGBA rgba;
rgba.set_rgba(1.0, 0.0, 0.0);
double factor = light_theme ? 0.5 : 0.35;
rgba.set_red(normal_color.get_red() + factor * (rgba.get_red() - normal_color.get_red()));
rgba.set_green(normal_color.get_green() + factor * (rgba.get_green() - normal_color.get_green()));
rgba.set_blue(normal_color.get_blue() + factor * (rgba.get_blue() - normal_color.get_blue()));
red_tag->property_foreground_rgba() = rgba;
rgba.set_rgba(0.0, 1.0, 0.0);
factor = 0.4;
rgba.set_red(normal_color.get_red() + factor * (rgba.get_red() - normal_color.get_red()));
rgba.set_green(normal_color.get_green() + factor * (rgba.get_green() - normal_color.get_green()));
rgba.set_blue(normal_color.get_blue() + factor * (rgba.get_blue() - normal_color.get_blue()));
green_tag->property_foreground_rgba() = rgba;
rgba.set_rgba(1.0, 1.0, 0.2);
factor = 0.5;
rgba.set_red(normal_color.get_red() + factor * (rgba.get_red() - normal_color.get_red()));
rgba.set_green(normal_color.get_green() + factor * (rgba.get_green() - normal_color.get_green()));
rgba.set_blue(normal_color.get_blue() + factor * (rgba.get_blue() - normal_color.get_blue()));
yellow_tag->property_foreground_rgba() = rgba;
rgba.set_rgba(0.0, 0.0, 1.0);
factor = light_theme ? 0.8 : 0.2;
rgba.set_red(normal_color.get_red() + factor * (rgba.get_red() - normal_color.get_red()));
rgba.set_green(normal_color.get_green() + factor * (rgba.get_green() - normal_color.get_green()));
rgba.set_blue(normal_color.get_blue() + factor * (rgba.get_blue() - normal_color.get_blue()));
blue_tag->property_foreground_rgba() = rgba;
rgba.set_rgba(1.0, 0.0, 1.0);
factor = light_theme ? 0.45 : 0.25;
rgba.set_red(normal_color.get_red() + factor * (rgba.get_red() - normal_color.get_red()));
rgba.set_green(normal_color.get_green() + factor * (rgba.get_green() - normal_color.get_green()));
rgba.set_blue(normal_color.get_blue() + factor * (rgba.get_blue() - normal_color.get_blue()));
magenta_tag->property_foreground_rgba() = rgba;
rgba.set_rgba(0.0, 1.0, 1.0);
factor = light_theme ? 0.35 : 0.35;
rgba.set_red(normal_color.get_red() + factor * (rgba.get_red() - normal_color.get_red()));
rgba.set_green(normal_color.get_green() + factor * (rgba.get_green() - normal_color.get_green()));
rgba.set_blue(normal_color.get_blue() + factor * (rgba.get_blue() - normal_color.get_blue()));
cyan_tag->property_foreground_rgba() = rgba;
// Set search match style: // Set search match style:
get_buffer()->get_tag_table()->foreach([](const Glib::RefPtr<Gtk::TextTag> &tag) { get_buffer()->get_tag_table()->foreach([](const Glib::RefPtr<Gtk::TextTag> &tag) {
if(tag->property_background_set()) { if(tag->property_background_set()) {
@ -374,23 +518,25 @@ bool Terminal::on_button_press_event(GdkEventButton *button_event) {
window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, button_event->x, button_event->y, location_x, location_y); window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, button_event->x, button_event->y, location_x, location_y);
get_iter_at_location(iter, location_x, location_y); get_iter_at_location(iter, location_x, location_y);
if(iter.has_tag(link_tag)) { if(iter.has_tag(link_tag)) {
auto start_iter = get_buffer()->get_iter_at_line(iter.get_line()); auto start = get_buffer()->get_iter_at_line(iter.get_line());
auto end_iter = start_iter; auto end = start;
while(!end_iter.ends_line() && end_iter.forward_char()) { if(!end.ends_line())
} end.forward_to_line_end();
auto link = find_link(get_buffer()->get_text(start_iter, end_iter).raw()); if(auto link = find_link(get_buffer()->get_text(start, end, false).raw())) {
if(link) {
auto path = filesystem::get_long_path(link->path); auto path = filesystem::get_long_path(link->path);
if(path.is_relative()) { if(path.is_relative()) {
if(Project::current) { auto project = Project::current;
if(!project)
project = Project::create();
if(project) {
boost::system::error_code ec; boost::system::error_code ec;
if(boost::filesystem::exists(Project::current->build->get_default_path() / path, ec)) if(boost::filesystem::exists(project->build->get_default_path() / path, ec))
path = Project::current->build->get_default_path() / path; path = project->build->get_default_path() / path;
else if(boost::filesystem::exists(Project::current->build->get_debug_path() / path, ec)) else if(boost::filesystem::exists(project->build->get_debug_path() / path, ec))
path = Project::current->build->get_debug_path() / path; path = project->build->get_debug_path() / path;
else if(boost::filesystem::exists(Project::current->build->project_path / path, ec)) else if(boost::filesystem::exists(project->build->project_path / path, ec))
path = Project::current->build->project_path / path; path = project->build->project_path / path;
else else
return Gtk::TextView::on_button_press_event(button_event); return Gtk::TextView::on_button_press_event(button_event);
} }
@ -435,7 +581,7 @@ bool Terminal::on_key_press_event(GdkEventKey *event) {
get_buffer()->place_cursor(get_buffer()->end()); get_buffer()->place_cursor(get_buffer()->end());
if(stdin_buffer.size() > 0 && get_buffer()->get_char_count() > 0) { if(stdin_buffer.size() > 0 && get_buffer()->get_char_count() > 0) {
auto iter = get_buffer()->end(); auto iter = get_buffer()->end();
iter--; iter.backward_char();
stdin_buffer.erase(stdin_buffer.size() - 1); stdin_buffer.erase(stdin_buffer.size() - 1);
get_buffer()->erase(iter, get_buffer()->end()); get_buffer()->erase(iter, get_buffer()->end());
} }

3
src/terminal.hpp

@ -45,9 +45,10 @@ private:
Dispatcher dispatcher; Dispatcher dispatcher;
Glib::RefPtr<Gtk::TextTag> bold_tag; Glib::RefPtr<Gtk::TextTag> bold_tag;
Glib::RefPtr<Gtk::TextTag> link_tag; Glib::RefPtr<Gtk::TextTag> link_tag;
Glib::RefPtr<Gtk::TextTag> invisible_tag;
Glib::RefPtr<Gtk::TextTag> red_tag, green_tag, yellow_tag, blue_tag, magenta_tag, cyan_tag;
Glib::RefPtr<Gdk::Cursor> link_mouse_cursor; Glib::RefPtr<Gdk::Cursor> link_mouse_cursor;
Glib::RefPtr<Gdk::Cursor> default_mouse_cursor; Glib::RefPtr<Gdk::Cursor> default_mouse_cursor;
size_t deleted_lines = 0;
struct Link { struct Link {
int start_pos, end_pos; int start_pos, end_pos;

2
tests/stubs/project.cpp

@ -1,3 +1,5 @@
#include "project.hpp" #include "project.hpp"
std::shared_ptr<Project::Base> Project::current; std::shared_ptr<Project::Base> Project::current;
std::shared_ptr<Project::Base> Project::create() { return nullptr; }

176
tests/terminal_test.cpp

@ -1,3 +1,4 @@
#include "config.hpp"
#include "terminal.hpp" #include "terminal.hpp"
#include <glib.h> #include <glib.h>
#include <gtksourceviewmm.h> #include <gtksourceviewmm.h>
@ -10,9 +11,21 @@
int main() { int main() {
auto app = Gtk::Application::create(); auto app = Gtk::Application::create();
Gsv::init(); Gsv::init();
auto &terminal = Terminal::get();
auto buffer = terminal.get_buffer();
{ {
auto link = Terminal::get().find_link("~/test/test.cc:7:41: error: expected ';' after expression."); Config::get().terminal.history_size = 1;
terminal.print("test");
assert(buffer->get_text() == "test");
terminal.print("\ntest2");
assert(buffer->get_text() == "test2");
Config::get().terminal.history_size = 10000;
}
// Testing links
{
auto link = terminal.find_link("~/test/test.cc:7:41: error: expected ';' after expression.");
assert(link); assert(link);
assert(link->start_pos == 0); assert(link->start_pos == 0);
assert(link->end_pos == 19); assert(link->end_pos == 19);
@ -21,7 +34,7 @@ int main() {
assert(link->line_index == 41); assert(link->line_index == 41);
} }
{ {
auto link = Terminal::get().find_link("Assertion failed: (false), function main, file ~/test/test.cc, line 15."); auto link = terminal.find_link("Assertion failed: (false), function main, file ~/test/test.cc, line 15.");
assert(link); assert(link);
assert(link->start_pos == 47); assert(link->start_pos == 47);
assert(link->end_pos == 70); assert(link->end_pos == 70);
@ -30,7 +43,7 @@ int main() {
assert(link->line_index == 1); assert(link->line_index == 1);
} }
{ {
auto link = Terminal::get().find_link("test: ~/examples/main.cpp:17: int main(int, char**): Assertion `false' failed."); auto link = terminal.find_link("test: ~/examples/main.cpp:17: int main(int, char**): Assertion `false' failed.");
assert(link); assert(link);
assert(link->start_pos == 6); assert(link->start_pos == 6);
assert(link->end_pos == 28); assert(link->end_pos == 28);
@ -39,7 +52,7 @@ int main() {
assert(link->line_index == 1); assert(link->line_index == 1);
} }
{ {
auto link = Terminal::get().find_link("ERROR:~/test/test.cc:36:int main(): assertion failed: (false)"); auto link = terminal.find_link("ERROR:~/test/test.cc:36:int main(): assertion failed: (false)");
assert(link); assert(link);
assert(link->start_pos == 6); assert(link->start_pos == 6);
assert(link->end_pos == 23); assert(link->end_pos == 23);
@ -47,4 +60,159 @@ int main() {
assert(link->line == 36); assert(link->line == 36);
assert(link->line_index == 1); assert(link->line_index == 1);
} }
{
auto link = terminal.find_link(" --> src/main.rs:16:4");
assert(link);
assert(link->start_pos == 6);
assert(link->end_pos == 22);
assert(link->path == "src/main.rs");
assert(link->line == 16);
assert(link->line_index == 4);
}
{
auto link = terminal.find_link(R"( File "/home/test/test.py", line 4, in <module>)");
assert(link);
assert(link->start_pos == 8);
assert(link->end_pos == 35);
assert(link->path == "/home/test/test.py");
assert(link->line == 4);
assert(link->line_index == 1);
}
// Testing print
{
terminal.clear();
terminal.print("test");
assert(buffer->get_text() == "test");
auto iter = buffer->begin();
assert(!(iter.has_tag(terminal.bold_tag) || iter.forward_to_tag_toggle(terminal.bold_tag)));
terminal.clear();
terminal.print("test", true);
assert(buffer->get_text() == "test");
iter = buffer->begin();
assert(iter.begins_tag(terminal.bold_tag));
iter.forward_chars(4);
assert(iter.ends_tag(terminal.bold_tag));
}
{
terminal.clear();
terminal.print("te\xff\xff\xffst");
assert(buffer->get_text() == "te???st");
}
{
terminal.clear();
terminal.print("~/test/test.cc:7:41: error: expected ';' after expression.\n");
assert(buffer->get_text() == "~/test/test.cc:7:41: error: expected ';' after expression.\n");
auto iter = buffer->begin();
assert(iter.begins_tag(terminal.link_tag));
iter.forward_chars(19);
assert(iter.ends_tag(terminal.link_tag));
}
{
terminal.clear();
terminal.print("~/test/test.cc:7:41: error: ");
terminal.print("expected ';' after expression.\n");
assert(buffer->get_text() == "~/test/test.cc:7:41: error: expected ';' after expression.\n");
auto iter = buffer->begin();
assert(iter.begins_tag(terminal.link_tag));
iter.forward_chars(19);
assert(iter.ends_tag(terminal.link_tag));
}
// Testing ansi colors
{
terminal.clear();
terminal.print("\e[31mtest\e[m\e[32mtest\e[m\e[33mtest\e[m\e[34mtest\e[m\e[35mtest\e[m\e[36mtest\e[m");
assert(buffer->get_text(true) == "\e[31mtest\e[m\e[32mtest\e[m\e[33mtest\e[m\e[34mtest\e[m\e[35mtest\e[m\e[36mtest\e[m");
assert(buffer->get_text(false) == "testtesttesttesttesttest");
auto iter = buffer->begin();
iter.forward_chars(5);
assert(iter.starts_tag(terminal.red_tag));
iter.forward_chars(4);
assert(iter.ends_tag(terminal.red_tag));
iter.forward_chars(8);
assert(iter.starts_tag(terminal.green_tag));
iter.forward_chars(4);
assert(iter.ends_tag(terminal.green_tag));
iter.forward_chars(8);
assert(iter.starts_tag(terminal.yellow_tag));
iter.forward_chars(4);
assert(iter.ends_tag(terminal.yellow_tag));
iter.forward_chars(8);
assert(iter.starts_tag(terminal.blue_tag));
iter.forward_chars(4);
assert(iter.ends_tag(terminal.blue_tag));
iter.forward_chars(8);
assert(iter.starts_tag(terminal.magenta_tag));
iter.forward_chars(4);
assert(iter.ends_tag(terminal.magenta_tag));
iter.forward_chars(8);
assert(iter.starts_tag(terminal.cyan_tag));
iter.forward_chars(4);
assert(iter.ends_tag(terminal.cyan_tag));
}
{
terminal.clear();
terminal.print("\e[31mte");
terminal.print("st\e[m");
assert(buffer->get_text(true) == "\e[31mtest\e[m");
assert(buffer->get_text(false) == "test");
auto iter = buffer->begin();
iter.forward_chars(5);
assert(iter.starts_tag(terminal.red_tag));
iter.forward_chars(4);
assert(iter.ends_tag(terminal.red_tag));
}
{
terminal.clear();
terminal.print("enable_\e[01;31m\e[Ktest\e[m\e[King");
assert(buffer->get_text(true) == "enable_\e[01;31m\e[Ktest\e[m\e[King");
assert(buffer->get_text(false) == "enable_testing");
auto iter = buffer->begin();
iter.forward_chars(15);
assert(iter.starts_tag(terminal.red_tag));
iter.forward_chars(7);
assert(iter.ends_tag(terminal.red_tag));
}
{
terminal.clear();
terminal.print("enable_\e[01;31m\e[Ktest\e[0m\e[King");
assert(buffer->get_text(true) == "enable_\e[01;31m\e[Ktest\e[0m\e[King");
assert(buffer->get_text(false) == "enable_testing");
auto iter = buffer->begin();
iter.forward_chars(15);
assert(iter.starts_tag(terminal.red_tag));
iter.forward_chars(7);
assert(iter.ends_tag(terminal.red_tag));
}
{
terminal.clear();
terminal.print("test\e[0m\e[7m\e[1m\e[31mtest\e[39m\e[22m\e[27m\e[0mtest");
assert(buffer->get_text(true) == "test\e[0m\e[7m\e[1m\e[31mtest\e[39m\e[22m\e[27m\e[0mtest");
assert(buffer->get_text(false) == "testtesttest");
auto iter = buffer->begin();
assert(iter.get_tags().empty());
iter.forward_visible_cursor_positions(3);
assert(iter.get_tags().empty());
iter.forward_visible_cursor_positions(1);
assert(iter.get_tags() == std::vector<Glib::RefPtr<Gtk::TextTag>>{terminal.red_tag});
iter.forward_visible_cursor_positions(3);
assert(iter.get_tags() == std::vector<Glib::RefPtr<Gtk::TextTag>>{terminal.red_tag});
iter.forward_visible_cursor_positions(1);
assert(iter.get_tags().empty());
}
{
terminal.clear();
terminal.print("\e[31mtest\e[39mtest");
assert(buffer->get_text(true) == "\e[31mtest\e[39mtest");
assert(buffer->get_text(false) == "testtest");
auto iter = buffer->begin();
iter.forward_visible_cursor_position();
assert(iter.starts_tag(terminal.red_tag));
iter.forward_visible_cursor_positions(3);
assert(iter.get_tags() == std::vector<Glib::RefPtr<Gtk::TextTag>>{terminal.red_tag});
iter.forward_visible_cursor_positions(1);
assert(iter.get_tags().empty());
}
} }

Loading…
Cancel
Save