Browse Source

Extend selection now works on HTML and JSX

merge-requests/404/merge
eidheim 5 years ago
parent
commit
cb2bc83044
  1. 248
      src/source.cpp
  2. 2
      src/source.hpp
  3. 128
      tests/source_test.cpp

248
src/source.cpp

@ -176,6 +176,8 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr<
is_c = true;
else if(language_id == "cpphdr" || language_id == "cpp")
is_cpp = true;
else if(language_id == "js" || language_id == "html")
is_js = true;
if(is_c || is_cpp) {
use_fixed_continuation_indenting = false;
@ -1254,7 +1256,8 @@ void Source::View::extend_selection() {
(*before_start == '<' && *end == '>') ||
(*before_start == '{' && *end == '}')) {
// Select expression from selected brackets
if(extend_expression(start, end)) {
if(!(*before_start == '<' && *end == '>' && is_js) &&
extend_expression(start, end)) {
get_buffer()->select_range(start, end);
return;
}
@ -1282,9 +1285,11 @@ void Source::View::extend_selection() {
int para_count = 0;
int square_count = 0;
int angle_count = 0;
int curly_count = 0;
auto start_comma_iter = get_buffer()->end();
auto start_angle_iter = get_buffer()->end();
auto start_angle_reversed_iter = get_buffer()->end();
while(start.backward_char()) {
if(*start == '(' && is_code_iter(start))
para_count++;
@ -1294,6 +1299,19 @@ void Source::View::extend_selection() {
square_count++;
else if(*start == ']' && is_code_iter(start))
square_count--;
else if(*start == '<' && is_code_iter(start)) {
if(!start_angle_iter && para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0)
start_angle_iter = start;
angle_count++;
}
else if(*start == '>' && is_code_iter(start)) {
auto prev = start;
if(!(prev.backward_char() && (*prev == '=' || *prev == '-'))) {
if(!start_angle_reversed_iter && para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0)
start_angle_reversed_iter = start;
angle_count--;
}
}
else if(*start == '{' && is_code_iter(start)) {
if(!start_sentence_iter &&
para_count == 0 && square_count == 0 && curly_count == 0) {
@ -1318,10 +1336,6 @@ void Source::View::extend_selection() {
para_count == 0 && square_count == 0 && curly_count == 0 &&
*start == ';' && is_code_iter(start))
start_sentence_iter = start;
else if(!start_angle_iter &&
para_count == 0 && square_count == 0 && curly_count == 0 &&
*start == '<' && is_code_iter(start))
start_angle_iter = start;
if(*start == ';' && is_code_iter(start)) {
ignore_comma = true;
start_comma_iter = get_buffer()->end();
@ -1332,9 +1346,11 @@ void Source::View::extend_selection() {
para_count = 0;
square_count = 0;
angle_count = 0;
curly_count = 0;
auto end_comma_iter = get_buffer()->end();
auto end_angle_iter = get_buffer()->end();
auto end_angle_reversed_iter = get_buffer()->end();
do {
if(*end == '(' && is_code_iter(end))
para_count++;
@ -1344,6 +1360,19 @@ void Source::View::extend_selection() {
square_count++;
else if(*end == ']' && is_code_iter(end))
square_count--;
else if(*end == '<' && is_code_iter(end)) {
if(!end_angle_reversed_iter && para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0)
end_angle_reversed_iter = end;
angle_count++;
}
else if(*end == '>' && is_code_iter(end)) {
auto prev = end;
if(!(prev.backward_char() && (*prev == '=' || *prev == '-'))) {
if(!end_angle_iter && para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0)
end_angle_iter = end;
angle_count--;
}
}
else if(*end == '{' && is_code_iter(end))
curly_count++;
else if(*end == '}' && is_code_iter(end)) {
@ -1353,6 +1382,8 @@ void Source::View::extend_selection() {
auto next = end_sentence_iter = end;
if(next.forward_char() && forward_to_code(next) && *next == ';')
end_sentence_iter = next;
else if(is_js && *next == '>')
end_sentence_iter = get_buffer()->end();
}
}
else if(!ignore_comma && !end_comma_iter &&
@ -1363,10 +1394,6 @@ void Source::View::extend_selection() {
para_count == 0 && square_count == 0 && curly_count == 0 &&
*end == ';' && is_code_iter(end))
end_sentence_iter = end;
else if(!end_angle_iter &&
para_count == 0 && square_count == 0 && curly_count == 0 &&
*end == '>' && is_code_iter(end))
end_angle_iter = end;
if(*end == ';' && is_code_iter(end)) {
ignore_comma = true;
start_comma_iter = get_buffer()->end();
@ -1376,10 +1403,156 @@ void Source::View::extend_selection() {
break;
} while(end.forward_char());
// Extend HTML/JSX
if(is_js) {
static std::vector<std::string> void_elements = {"area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"};
auto get_element = [this](Gtk::TextIter iter) {
auto start = iter;
auto end = iter;
while(iter.backward_char() && (is_token_char(*iter) || *iter == '.'))
start = iter;
while((is_token_char(*end) || *end == '.') && end.forward_char()) {
}
return get_buffer()->get_text(start, end);
};
Gtk::TextIter start = get_buffer()->end(), end = get_buffer()->end();
auto start_stored_prev = start_stored;
if(start_angle_iter && end_angle_iter) { // If inside angle brackets in for instance <selection here>
auto next = start_angle_iter;
next.forward_char();
auto prev = end_angle_iter;
prev.backward_char();
auto element = get_element(next);
if(*next == '/') {
start = end = start_angle_iter;
start.backward_char();
}
else if(*prev == '/' || std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) {
end_angle_iter.forward_char();
get_buffer()->select_range(start_angle_iter, end_angle_iter);
return;
}
else {
start = end = end_angle_iter;
end.forward_char();
}
}
else if(start_stored_prev.backward_char() && *start_stored_prev == '<' && *end_stored == '>') { // Matches for instance <div>, where div is selected
auto element = get_element(start_stored);
if(std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) {
auto next = end_stored;
next.forward_char();
get_buffer()->select_range(start_stored_prev, next);
return;
}
start = end = end_stored;
end.forward_char();
}
else if(start_angle_reversed_iter && end_angle_reversed_iter) { // If not inside angle brackets, for instance <>selection here</>
start = start_angle_reversed_iter;
end = end_angle_reversed_iter;
}
if(start && end) {
Gtk::TextIter start_children, end_children;
auto iter = start;
int depth = 0;
// Search backward for opening element
while(find_open_symbol_backward(iter, iter, '>', '<') && iter.backward_char()) { // Backward to > (end of opening element)
start_children = iter;
start_children.forward_chars(2);
auto prev = iter;
prev.backward_char();
bool no_child_element = *iter == '/' && *prev != '<'; // Excludes </> as it is always closing element of <>
if(find_open_symbol_backward(iter, iter, '<', '>')) { // Backward to <
if(!no_child_element) {
iter.forward_char();
if(*iter == '/') {
--depth;
if(!iter.backward_chars(2))
break;
continue;
}
auto element = get_element(iter);
if(std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) {
if(!iter.backward_chars(2))
break;
continue;
}
else if(depth == 0) {
iter.backward_char();
auto start = iter;
iter = end;
// Search forward for closing element
int depth = 0;
while(find_close_symbol_forward(iter, iter, '>', '<') && iter.forward_char()) { // Forward to < (start of closing element)
end_children = iter;
end_children.backward_char();
if(*iter == '/') {
if(iter.forward_char() && find_close_symbol_forward(iter, iter, '<', '>') && iter.forward_char()) { // Forward to >
if(depth == 0) {
if(start_children <= start_stored && end_children >= end_stored && (start_children != start_stored || end_children != end_stored)) // Select children
get_buffer()->select_range(start_children, end_children);
else
get_buffer()->select_range(start, iter);
return;
}
else {
--depth;
continue;
}
}
else
break;
}
else {
auto element = get_element(iter);
if(std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) {
if(!(find_close_symbol_forward(iter, iter, '<', '>') && iter.forward_char())) // Forward to >
break;
continue;
}
else if(find_close_symbol_forward(iter, iter, '<', '>') && iter.backward_char()) { // Forward to >
if(*iter == '/') {
iter.forward_chars(2);
continue;
}
else {
++depth;
iter.forward_chars(2);
continue;
}
}
else
break;
}
}
break;
}
else {
++depth;
if(!iter.backward_chars(2))
break;
continue;
}
}
else {
if(!iter.backward_char())
break;
}
}
else
break;
}
}
}
// Test for <> used for template arguments
if(start_angle_iter && end_angle_iter && is_template_arguments(start_angle_iter, end_angle_iter)) {
start = start_angle_iter;
end = end_angle_iter;
start_angle_iter.forward_char();
get_buffer()->select_range(start_angle_iter, end_angle_iter);
return;
}
// Test for matching brackets and try select regions within brackets separated by ','
@ -1564,8 +1737,9 @@ void Source::View::extend_selection() {
if(start == start_stored && end == end_stored) { // In case of no change due to inbalanced brackets
previous_extended_selections.pop_back();
if(!start.backward_char() && !end.forward_char())
if(!start.backward_char() && !end.forward_char()) {
return;
}
get_buffer()->select_range(start, end);
extend_selection();
return;
@ -1865,9 +2039,30 @@ bool Source::View::find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter &
else {
long curly_count = 0;
do {
if(curly_count == 0 && *iter == positive_char && is_code_iter(iter))
if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) {
if((is_c || is_cpp) && positive_char == '>') {
auto prev = iter;
if(prev.backward_char() && *prev == '-')
continue;
}
else if(is_js && positive_char == '>') {
auto prev = iter;
if(prev.backward_char() && *prev == '=')
continue;
}
count++;
}
else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) {
if((is_c || is_cpp) && negative_char == '>') {
auto prev = iter;
if(prev.backward_char() && *prev == '-')
continue;
}
else if(is_js && negative_char == '>') {
auto prev = iter;
if(prev.backward_char() && *prev == '=')
continue;
}
if(count == 0) {
found_iter = iter;
return true;
@ -1906,14 +2101,35 @@ bool Source::View::find_open_symbol_backward(Gtk::TextIter iter, Gtk::TextIter &
long curly_count = 0;
do {
if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) {
if((is_c || is_cpp) && positive_char == '>') {
auto prev = iter;
if(prev.backward_char() && *prev == '-')
continue;
}
else if(is_js && positive_char == '>') {
auto prev = iter;
if(prev.backward_char() && *prev == '=')
continue;
}
if(count == 0) {
found_iter = iter;
return true;
}
count++;
}
else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter))
else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) {
if((is_c || is_cpp) && negative_char == '>') {
auto prev = iter;
if(prev.backward_char() && *prev == '-')
continue;
}
else if(is_js && negative_char == '>') {
auto prev = iter;
if(prev.backward_char() && *prev == '=')
continue;
}
count--;
}
else if(*iter == '{' && is_code_iter(iter)) {
if(curly_count == 0)
return false;
@ -2435,7 +2651,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) {
// to
// <div>
// CURSORtest
if(*condition_iter == '>' && language && (language->get_id() == "js" || language->get_id() == "html")) {
if(*condition_iter == '>' && is_js) {
auto prev = condition_iter;
Gtk::TextIter open_element_iter;
if(prev.backward_char() && backward_to_code(prev) && *prev != '/' &&
@ -2451,7 +2667,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) {
return get_buffer()->get_text(start, end);
};
auto open_element_token = get_element(open_element_iter);
std::vector<std::string> void_elements = {"area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"};
static std::vector<std::string> void_elements = {"area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"};
if(std::none_of(void_elements.begin(), void_elements.end(), [&open_element_token](const std::string &e) { return e == open_element_token; })) {
auto close_element_iter = iter;
// If cursor is placed between open and close tag

2
src/source.hpp

@ -176,6 +176,8 @@ namespace Source {
bool is_c = false;
bool is_cpp = false;
/// Set to true if language is html or js (including typescript)
bool is_js = false;
private:
void setup_signals();

128
tests/source_test.cpp

@ -360,6 +360,134 @@ int main() {
view.shrink_selection();
g_assert(view.get_selected_text() == "{\n test;\n }");
}
{
auto buffer = view.get_buffer();
view.is_js = true;
Gtk::TextIter start, end;
buffer->set_text(R"(render(
<div></div>
);)");
view.place_cursor_at_line_offset(1, 7);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 13);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0);
view.place_cursor_at_line_offset(1, 4);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 3 && end.get_line() == 1 && end.get_line_offset() == 6);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 13);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0);
buffer->set_text(R"(render(
<div id="test">test test</div>
);)");
view.place_cursor_at_line_offset(1, 18);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 17 && end.get_line() == 1 && end.get_line_offset() == 21);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 17 && end.get_line() == 1 && end.get_line_offset() == 26);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 32);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0);
view.place_cursor_at_line_offset(1, 4);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 3 && end.get_line() == 1 && end.get_line_offset() == 6);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 32);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0);
buffer->set_text(R"(render(
<div onClick={() => {}}></div>
);)");
view.place_cursor_at_line_offset(1, 6);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 32);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0);
buffer->set_text(R"(render(
<div>
<div></div>
<div></div>
</div>
);)");
view.place_cursor_at_line_offset(2, 8);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 2 && start.get_line_offset() == 4 && end.get_line() == 2 && end.get_line_offset() == 15);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 7 && end.get_line() == 4 && end.get_line_offset() == 2);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 4 && end.get_line_offset() == 8);
view.place_cursor_at_line_offset(3, 8);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 3 && start.get_line_offset() == 4 && end.get_line() == 3 && end.get_line_offset() == 15);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 7 && end.get_line() == 4 && end.get_line_offset() == 2);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 4 && end.get_line_offset() == 8);
buffer->set_text(R"(render(
<div>
<div></div>
<img src="test">
<Test/>
<br>
<div></div>
</div>
);)");
view.place_cursor_at_line_offset(2, 8);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 2 && start.get_line_offset() == 4 && end.get_line() == 2 && end.get_line_offset() == 15);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 7 && end.get_line() == 7 && end.get_line_offset() == 2);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 7 && end.get_line_offset() == 8);
view.place_cursor_at_line_offset(6, 8);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 6 && start.get_line_offset() == 4 && end.get_line() == 6 && end.get_line_offset() == 15);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 7 && end.get_line() == 7 && end.get_line_offset() == 2);
view.extend_selection();
buffer->get_selection_bounds(start, end);
g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 7 && end.get_line_offset() == 8);
view.is_js = false;
}
}
// Snippet tests

Loading…
Cancel
Save