Files
textmate/Frameworks/selection/src/selection.cc
Allan Odgaard fa6659db28 Update dyn.caret/selection scopes
If there is one or more selections:

    dyn.selection

If there is a single zero-width selection:

    dyn.caret.mixed.columnar

If there are multiple carets and/or selections:

    dyn.caret.mixed

When there is only a single caret or a single continuous selection the left scope may contain:

    dyn.caret.begin.line
    dyn.caret.begin.document

Likewise the right scope may contain:

    dyn.caret.end.line
    dyn.caret.end.document
2012-09-30 15:25:45 +02:00

1254 lines
46 KiB
C++

#include "selection.h"
#include <buffer/buffer.h>
#include <bundles/bundles.h>
#include <regexp/find.h>
#include <text/classification.h>
#include <text/utf8.h>
#include <text/ctype.h>
#include <text/types.h>
#include <text/tokenize.h>
namespace ng
{
static size_t count_columns (buffer_t const& buffer, index_t caret)
{
size_t const tabSize = buffer.indent().tab_size();
std::string const str = buffer.substr(buffer.begin(buffer.convert(caret.index).line), caret.index);
size_t len = 0;
citerate(ch, diacritics::make_range(str.data(), str.data() + str.size()))
len += *ch == '\t' ? tabSize - (len % tabSize) : (text::is_east_asian_width(*ch) ? 2 : 1);
return len + caret.carry;
}
static index_t at_column (buffer_t const& buffer, size_t line, size_t column)
{
size_t const tabSize = buffer.indent().tab_size();
size_t caret = buffer.begin(line), len = 0;
std::string const str = buffer.substr(caret, buffer.eol(line));
citerate(ch, diacritics::make_range(str.data(), str.data() + str.size()))
{
if(len == column)
return caret + (&ch - str.data());
size_t chWidth = *ch == '\t' ? tabSize - (len % tabSize) : (text::is_east_asian_width(*ch) ? 2 : 1);
if(len + chWidth > column || *ch == '\n')
return index_t(caret + (&ch - str.data()), column - len);
len += chWidth;
}
return index_t(caret + str.size(), column - len);
}
std::string const kCharacterClassWord = "word";
std::string const kCharacterClassSpace = "space";
std::string const kCharacterClassOther = "other";
std::string const kCharacterClassUnknown = "unknown";
std::string character_class (buffer_t const& buffer, size_t index)
{
bundles::item_ptr match;
plist::any_t value = bundles::value_for_setting("characterClass", buffer.scope(index), &match);
if(match)
return boost::get<std::string>(value);
else if(text::is_word_char(buffer[index]))
return kCharacterClassWord;
else if(text::is_whitespace(buffer[index]))
return kCharacterClassSpace;
return kCharacterClassOther;
}
static bool is_part_of_word (buffer_t const& buffer, size_t index)
{
return character_class(buffer, index) != kCharacterClassSpace && character_class(buffer, index) != kCharacterClassOther;
}
static size_t extend_scope_left (buffer_t const& buffer, size_t caret, scope::scope_t const& scope)
{
while(caret && buffer.scope(caret).left.has_prefix(scope))
caret -= buffer[caret-1].size();
return caret;
}
static size_t extend_scope_right (buffer_t const& buffer, size_t caret, scope::scope_t const& scope)
{
while(caret < buffer.size() && buffer.scope(caret).right.has_prefix(scope))
caret += buffer[caret].size();
return caret;
}
static ranges_t sanitize (buffer_t const& buffer, ranges_t const& selection)
{
// TODO Ensure ranges do not overlap.
std::set<ng::range_t> set;
ranges_t res;
iterate(range, selection)
{
range_t r(index_t(buffer.sanitize_index(range->first.index), range->first.carry), index_t(buffer.sanitize_index(range->last.index), range->last.carry), range->columnar, range->freehanded, range->unanchored);
if(set.find(r) == set.end())
{
res.push_back(r);
set.insert(r);
}
}
return res;
}
static index_t cap (buffer_t const& buf, text::pos_t const& pos)
{
size_t line = oak::cap<size_t>(0, pos.line, buf.lines()-1);
size_t col = oak::cap<size_t>(0, pos.column, buf.eol(line) - buf.begin(line));
index_t res = buf.sanitize_index(buf.convert(text::pos_t(line, col)));
if(pos.offset && res.index < buf.size() && buf[res.index] == "\n")
res.carry = pos.offset;
return res;
}
static range_t cap (buffer_t const& buf, text::range_t const& range)
{
return range_t(cap(buf, range.from), cap(buf, range.to), range.columnar, false, true);
}
ranges_t convert (buffer_t const& buf, text::selection_t const& sel)
{
ranges_t res;
iterate(range, sel)
res.push_back(cap(buf, *range));
return sanitize(buf, res);
}
// =======================
// = Typing Pair Support =
// =======================
static std::vector< std::pair<std::string, std::string> > character_pairs (scope::context_t const& scope, std::string const& key)
{
std::vector< std::pair<std::string, std::string> > res;
plist::any_t value = bundles::value_for_setting(key, scope);
if(plist::array_t const* array = boost::get<plist::array_t>(&value))
{
iterate(pair, *array)
{
if(plist::array_t const* value = boost::get<plist::array_t>(&*pair))
{
std::string const* a1 = value->size() == 2 ? boost::get<std::string>(&(*value)[0]) : NULL;
std::string const* a2 = value->size() == 2 ? boost::get<std::string>(&(*value)[1]) : NULL;
if(a1 && a2 && *a1 != *a2)
res.push_back(std::make_pair(*a1, *a2));
}
}
}
else
{
res.push_back(std::make_pair("(", ")"));
res.push_back(std::make_pair("{", "}"));
res.push_back(std::make_pair("[", "]"));
}
return res;
}
namespace
{
struct character_pair_t
{
character_pair_t () { }
character_pair_t (std::string const& first, std::string const& second, bool matchedFirst) : first(first), second(second), matched_first(matchedFirst), initialized(true) { }
explicit operator bool () const { return initialized; }
std::string first, second;
bool matched_first, initialized = false;
};
}
static bool does_match (buffer_t const& buffer, size_t index, std::string const& ch)
{
return index + ch.size() <= buffer.size() && ch == buffer.substr(index, index + ch.size());
}
static character_pair_t first_match (buffer_t const& buffer, size_t index, std::vector< std::pair<std::string, std::string> > const& pairs)
{
citerate(pair, pairs)
{
if(does_match(buffer, index, pair->first))
return character_pair_t(pair->first, pair->second, true);
else if(does_match(buffer, index, pair->second))
return character_pair_t(pair->first, pair->second, false);
}
return character_pair_t();
}
static size_t begin_of_typing_pair (buffer_t const& buffer, size_t caret, bool moveToBefore)
{
size_t orgCaret = caret;
auto pairs = character_pairs(buffer.scope(caret), "highlightPairs");
bool skip = false;
while(0 < caret)
{
caret -= buffer[caret-1].size();
auto pair = first_match(buffer, caret, pairs);
if(!pair)
continue;
if(pair.matched_first)
{
if(caret + pair.first.size() != orgCaret || moveToBefore)
return caret + (skip || moveToBefore || orgCaret < caret + pair.first.size() ? 0 : pair.first.size());
continue;
}
if(!moveToBefore && orgCaret <= caret + pair.second.size())
{
skip = true;
continue;
}
std::string const& openCh = pair.first;
std::string const& closeCh = pair.second;
for(size_t balance = 1; balance != 0 && 0 < caret; )
{
caret -= buffer[caret-1].size();
if(does_match(buffer, caret, openCh))
--balance;
else if(does_match(buffer, caret, closeCh))
++balance;
}
}
return orgCaret;
}
static size_t end_of_typing_pair (buffer_t const& buffer, size_t caret, bool moveToAfter)
{
size_t orgCaret = caret;
auto pairs = character_pairs(buffer.scope(caret), "highlightPairs");
bool skipEnd = false;
if(!moveToAfter)
{
if(auto pair = first_match(buffer, caret, pairs))
{
skipEnd = pair.matched_first;
caret += pair.matched_first ? pair.first.size() : pair.second.size();
}
}
while(caret < buffer.size())
{
auto pair = first_match(buffer, caret, pairs);
if(!pair)
{
caret += buffer[caret].size();
continue;
}
if(!pair.matched_first)
return caret + (skipEnd || moveToAfter ? pair.second.size() : 0);
std::string const& openCh = pair.first;
std::string const& closeCh = pair.second;
caret += openCh.size();
for(size_t balance = 1; balance != 0 && caret < buffer.size(); )
{
if(does_match(buffer, caret, closeCh))
{
--balance;
caret += closeCh.size();
}
else if(does_match(buffer, caret, openCh))
{
++balance;
caret += openCh.size();
}
else
{
caret += buffer[caret].size();
}
}
}
return orgCaret;
}
// ====================
// = Columnar Support =
// ====================
bool not_empty (buffer_t const& buffer, ranges_t const& selection)
{
bool isEmpty = true;
iterate(range, selection)
isEmpty = isEmpty && (range->empty() || (range->columnar && count_columns(buffer, range->first) == count_columns(buffer, range->last)));
return !isEmpty;
}
bool multiline (buffer_t const& buffer, ranges_t const& selection)
{
bool isMultiline = false;
iterate(range, selection)
isMultiline = isMultiline || buffer.convert(range->first.index).line != buffer.convert(range->last.index).line;
return isMultiline;
}
ranges_t dissect_columnar (buffer_t const& buffer, ranges_t const& selection)
{
ranges_t res;
iterate(range, selection)
{
if(range->columnar && !range->empty())
{
size_t colA = count_columns(buffer, range->first);
size_t colB = count_columns(buffer, range->last);
size_t lineA = buffer.convert(range->first.index).line;
size_t lineB = buffer.convert(range->last.index).line;
size_t fromLine = std::min(lineA, lineB), fromCol = std::min(colA, colB);
size_t toLine = std::max(lineA, lineB), toCol = std::max(colA, colB);
for(size_t n = fromLine; n <= toLine; ++n)
{
index_t from = at_column(buffer, n, fromCol), to = at_column(buffer, n, toCol);
res.push_back(range_t(from, to, false, from.carry || to.carry));
}
}
else
{
res.push_back(range_t(range->min(), range->max(), range->columnar, range->freehanded));
}
}
return res;
}
ranges_t toggle_columnar (ranges_t const& selection)
{
ranges_t res = selection;
if(res.size() != 0)
{
range_t& last = res.last();
last = range_t(last.first, last.last, !last.empty() && !last.columnar, last.freehanded);
}
return res;
}
// =================
// = Move Caret(s) =
// =================
static bool all_spaces (buffer_t const& buffer, size_t from, size_t to)
{
bool allSpaces = true;
while(from < to && allSpaces)
allSpaces = allSpaces && buffer[from++] == " ";
return allSpaces;
}
static bool all_whitespace (buffer_t const& buffer, size_t from, size_t to)
{
bool res = true;
static std::string const whitespaceChars[] = { " ", "\t", "\n" };
while(from < to && res)
res = res && oak::contains(std::begin(whitespaceChars), std::end(whitespaceChars), buffer[from++]);
return res;
}
static size_t end_of_leading_indent (buffer_t const& buffer, size_t line)
{
size_t bol = buffer.begin(line);
size_t eol = buffer.eol(line);
while(bol < eol && isspace(utf8::to_ch(buffer[bol])))
++bol;
return bol;
}
static index_t move (buffer_t const& buffer, index_t const& index, move_unit_type unit, layout_movement_t const* layout)
{
size_t const caret = index.index;
size_t const line = buffer.convert(caret).line;
if((unit == kSelectionMoveLeft || unit == kSelectionMoveRight) && buffer.indent().soft_tabs())
{
size_t const bol = buffer.begin(line);
if((caret - bol) % buffer.indent().indent_size() == 0)
{
if(all_spaces(buffer, bol, caret))
{
size_t const tabSize = buffer.indent().tab_size();
if(unit == kSelectionMoveLeft && caret != bol)
return caret - tabSize;
else if(unit == kSelectionMoveRight && caret + tabSize <= buffer.eol(line) && all_spaces(buffer, caret, caret + tabSize))
return caret + tabSize;
}
}
}
switch(unit)
{
case kSelectionMoveToBeginOfDocument: return 0;
case kSelectionMoveToEndOfDocument: return buffer.size();
case kSelectionMoveToBeginOfParagraph: return buffer.begin(line);
case kSelectionMoveToEndOfParagraph: return buffer.eol(line);
case kSelectionMoveToBeginOfLine: return buffer.begin(line);
case kSelectionMoveToEndOfLine: return buffer.eol(line);
case kSelectionMoveToBeginOfSoftLine: return layout ? layout->index_at_bol_for(caret) : buffer.begin(line);
case kSelectionMoveToEndOfSoftLine: return layout ? layout->index_at_eol_for(caret) : buffer.eol(line);
case kSelectionMoveUp: return layout ? layout->index_above(index) : (line ? at_column(buffer, line-1, count_columns(buffer, index)) : 0);
case kSelectionMoveDown: return layout ? layout->index_below(index) : (line+1 < buffer.lines() ? at_column(buffer, line+1, count_columns(buffer, index)) : buffer.size());
case kSelectionMoveLeft: return layout ? layout->index_left_of(caret) : (caret ? caret - buffer[caret-1].size() : caret);
case kSelectionMoveRight: return layout ? layout->index_right_of(caret) : (caret < buffer.size() ? caret + buffer[caret].size() : caret);
case kSelectionMoveToBeginOfTypingPair: return begin_of_typing_pair(buffer, caret, false);
case kSelectionMoveToEndOfTypingPair: return end_of_typing_pair(buffer, caret, false);
case kSelectionMovePageUp: return layout ? layout->page_up_for(index) : index;
case kSelectionMovePageDown: return layout ? layout->page_down_for(index) : index;
case kSelectionMoveToBeginOfIndentedLine:
{
size_t bol = buffer.begin(line);
size_t eoi = end_of_leading_indent(buffer, line);
return eoi < caret ? eoi : bol;
}
break;
case kSelectionMoveToEndOfIndentedLine:
{
size_t eoi = end_of_leading_indent(buffer, line);
size_t eol = buffer.eol(line);
return caret < eoi ? eoi : eol;
}
break;
case kSelectionMoveToBeginOfHardParagraph:
{
if(line == 0)
return 0;
for(size_t n = line-1; n > 0; --n)
{
std::string const& line = buffer.substr(buffer.begin(n), buffer.end(n));
if(text::is_blank(line.data(), line.data() + line.size()))
return buffer.begin(n+1);
}
return 0;
}
break;
case kSelectionMoveToEndOfHardParagraph:
{
for(size_t n = line+1; n < buffer.lines(); ++n)
{
std::string const& line = buffer.substr(buffer.begin(n), buffer.end(n));
if(text::is_blank(line.data(), line.data() + line.size()))
return buffer.begin(n);
}
return buffer.size();
}
break;
case kSelectionMoveFreehandedLeft:
{
if(index.carry != 0)
return index_t(caret, index.carry - 1);
else if(caret != 0 && buffer[caret-1] == "\t")
return at_column(buffer, line, count_columns(buffer, caret) - 1);
else
return move(buffer, index, kSelectionMoveLeft, layout); // FIXME With soft tabs this might move a full tab stop.
}
break;
case kSelectionMoveFreehandedRight:
{
if(caret == buffer.size() || buffer[caret] == "\n")
return index_t(caret, index.carry + 1);
else if(buffer[caret] == "\t")
return at_column(buffer, line, count_columns(buffer, caret) + index.carry + 1);
else
return move(buffer, index, kSelectionMoveRight, layout); // FIXME With soft tabs this might move a full tab stop.
}
break;
case kSelectionMoveToBeginOfWord:
{
size_t i = caret;
size_t bol = buffer.begin(line);
bol = i == bol && line != 0 ? buffer.begin(line-1) : bol;
if(i == bol)
return bol;
std::string charType = character_class(buffer, i-1);
while(bol < i && character_class(buffer, i-1) == charType)
i -= buffer[i-1].size();
if((charType == kCharacterClassSpace || charType == kCharacterClassOther) && bol < i && i + buffer[i].size() == caret)
{
std::string charType = character_class(buffer, i-1);
while(bol < i && character_class(buffer, i-1) == charType)
i -= buffer[i-1].size();
}
return i;
}
break;
case kSelectionMoveToEndOfWord:
{
size_t i = caret;
size_t eol = buffer.eol(line);
eol = caret == eol && line+1 < buffer.lines() ? buffer.eol(line+1) : eol;
if(caret == eol)
return eol;
std::string charType = character_class(buffer, i);
while(i < eol && character_class(buffer, i) == charType)
i += buffer[i].size();
if((charType == kCharacterClassSpace || charType == kCharacterClassOther) && i < eol && i == caret + buffer[caret].size())
{
charType = character_class(buffer, i);
while(i < eol && character_class(buffer, i) == charType)
i += buffer[i].size();
}
return i;
}
break;
case kSelectionMoveToBeginOfSubWord:
{
static regexp::pattern_t ptrn("(\\p{Upper}\\p{Lower}+|\\p{Upper}+|\\p{Lower}+)[^\\p{Upper}\\p{Lower}]?$|[^\\p{Upper}\\p{Lower}]+$");
size_t n = line && caret == buffer.begin(line) ? line-1 : line;
std::string const& line = buffer.substr(buffer.begin(n), caret);
if(regexp::match_t const& m = search(ptrn, line.data(), line.data() + line.size()))
return buffer.begin(n) + m.begin();
}
break;
case kSelectionMoveToEndOfSubWord:
{
static regexp::pattern_t ptrn("\\G([^\\p{Upper}\\p{Lower}]?("
"\\p{Upper}\\p{Upper}+?(?=\\p{Upper}\\p{Lower})" // NS‸String
"|\\p{Upper}\\p{Upper}+" // NDEBUG‸
"|\\p{Upper}\\p{Lower}*" // Camel‸Case || Camel‸_case
"|\\p{Lower}+" // camel‸Case || camel‸_case
")|[^\\p{Upper}\\p{Lower}]*)"); // ‸leading_whitespace
size_t n = line+1 < buffer.lines() && caret == buffer.eol(line) ? line+1 : line;
std::string const& line = buffer.substr(caret, buffer.eol(n));
if(regexp::match_t const& m = search(ptrn, line.data(), line.data() + line.size()))
return caret + m.end();
}
break;
case kSelectionMoveToBeginOfColumn:
{
size_t index = caret;
if(index == buffer.size() || !is_part_of_word(buffer, index))
{
while(buffer.convert(index).column && !is_part_of_word(buffer, index-1))
index -= buffer[index-1].size();
}
while(buffer.convert(index).column && is_part_of_word(buffer, index-1))
index -= buffer[index-1].size();
size_t orgCol = count_columns(buffer, caret);
size_t col = count_columns(buffer, index);
size_t n = line;
for(; n != 0; --n)
{
index_t newIndex = at_column(buffer, n-1, col);
if(newIndex.carry || (col && is_part_of_word(buffer, newIndex.index-1)) || !is_part_of_word(buffer, newIndex.index))
break;
}
if(n && n == line)
{
while(--n != 0)
{
index_t newIndex = at_column(buffer, n, orgCol);
if(!newIndex.carry && ((orgCol && is_part_of_word(buffer, newIndex.index-1)) || is_part_of_word(buffer, newIndex.index)))
break;
}
}
return at_column(buffer, n, orgCol);
}
break;
case kSelectionMoveToEndOfColumn:
{
size_t index = caret;
if(index == buffer.size() || !is_part_of_word(buffer, index))
{
while(buffer.convert(index).column && !is_part_of_word(buffer, index-1))
index -= buffer[index-1].size();
}
while(buffer.convert(index).column && is_part_of_word(buffer, index-1))
index -= buffer[index-1].size();
size_t orgCol = count_columns(buffer, caret);
size_t col = count_columns(buffer, index);
size_t n = line;
for(; n+1 != buffer.lines(); ++n)
{
index_t newIndex = at_column(buffer, n+1, col);
if(newIndex.carry || newIndex.index == buffer.size() || (col && is_part_of_word(buffer, newIndex.index-1)) || !is_part_of_word(buffer, newIndex.index))
break;
}
if(n+1 != buffer.lines() && n == line)
{
while(++n != buffer.lines()-1)
{
index_t newIndex = at_column(buffer, n, orgCol);
if(!newIndex.carry && newIndex.index != buffer.size() && ((orgCol && is_part_of_word(buffer, newIndex.index-1)) || is_part_of_word(buffer, newIndex.index)))
break;
}
}
return at_column(buffer, n, orgCol);
}
break;
}
return index;
}
ranges_t move (buffer_t const& buffer, ranges_t const& selection, move_unit_type const orgUnit, layout_movement_t const* layout)
{
static std::set<move_unit_type> const leftward = { kSelectionMoveLeft, kSelectionMoveFreehandedRight, kSelectionMoveToBeginOfLine, kSelectionMoveToBeginOfParagraph, kSelectionMoveToBeginOfTypingPair, kSelectionMoveToBeginOfSoftLine, kSelectionMoveToBeginOfSubWord, kSelectionMoveToBeginOfWord };
static std::set<move_unit_type> const rightward = { kSelectionMoveRight, kSelectionMoveFreehandedLeft, kSelectionMoveToEndOfLine, kSelectionMoveToEndOfParagraph, kSelectionMoveToEndOfTypingPair, kSelectionMoveToEndOfSoftLine, kSelectionMoveToEndOfSubWord, kSelectionMoveToEndOfWord };
bool isLeftward = leftward.find(orgUnit) != leftward.end();
bool isRightward = rightward.find(orgUnit) != rightward.end();
ranges_t res;
citerate(range, isLeftward || isRightward ? dissect_columnar(buffer, selection) : selection)
{
move_unit_type unit = orgUnit;
index_t index = range->last;
bool freehanded = range->freehanded;
if(!range->empty())
{
if(isLeftward || isRightward)
{
index = isLeftward ? range->min() : range->max();
static std::set<move_unit_type> const left_right = { kSelectionMoveLeft, kSelectionMoveRight, kSelectionMoveFreehandedRight, kSelectionMoveFreehandedLeft };
if(left_right.find(unit) != left_right.end())
unit = kSelectionMoveNowhere;
}
else if(!range->columnar && (unit == kSelectionMoveUp || unit == kSelectionMoveDown) && buffer.convert(range->first.index).line != buffer.convert(range->last.index).line)
{
index = unit == kSelectionMoveUp ? range->min() : range->max();
if(unit == kSelectionMoveDown && buffer.begin(buffer.convert(index.index).line) == index.index)
unit = kSelectionMoveNowhere;
}
}
if(freehanded)
{
if(unit == kSelectionMoveLeft)
unit = kSelectionMoveFreehandedLeft;
else if(unit == kSelectionMoveRight)
unit = kSelectionMoveFreehandedRight;
}
switch(unit)
{
case kSelectionMoveToBeginOfSelection: index = range->min(); break;
case kSelectionMoveToEndOfSelection: index = range->max(); break;
default: index = move(buffer, index, unit, layout); break;
}
if(freehanded && orgUnit == kSelectionMoveLeft && range->empty() && buffer.convert(index.index).line + 1 == buffer.convert(range->last.index).line)
freehanded = false;
res.push_back(range_t(index, index, false, freehanded));
}
if(res.size() > 1 && (orgUnit == kSelectionMoveUp || orgUnit == kSelectionMoveDown))
{
index_t min(SIZE_T_MAX), max(0);
iterate(range, res)
{
min = std::min(min, range->min());
max = std::max(max, range->max());
}
res = orgUnit == kSelectionMoveUp ? min : max;
}
return sanitize(buffer, res);
}
// ====================
// = Extend Selection =
// ====================
static range_t extend (buffer_t const& buffer, range_t const& range, select_unit_type unit, layout_movement_t const* layout)
{
index_t first = range.first;
index_t last = range.last;
index_t const& min = range.min();
index_t const& max = range.max();
if(first == last && !range.freehanded)
first.carry = last.carry = 0;
if(range.unanchored)
{
static std::set<select_unit_type> towardBegin = { kSelectionExtendLeft, kSelectionExtendFreehandedLeft, kSelectionExtendUp, kSelectionExtendToBeginOfWord, kSelectionExtendToBeginOfSubWord, kSelectionExtendToBeginOfSoftLine, kSelectionExtendToBeginOfIndentedLine, kSelectionExtendToBeginOfLine, kSelectionExtendToBeginOfParagraph, kSelectionExtendToBeginOfColumn, kSelectionExtendToBeginOfDocument, kSelectionExtendPageUp };
static std::set<select_unit_type> towardEnd = { kSelectionExtendRight, kSelectionExtendFreehandedRight, kSelectionExtendDown, kSelectionExtendToEndOfWord, kSelectionExtendToEndOfSubWord, kSelectionExtendToEndOfSoftLine, kSelectionExtendToEndOfIndentedLine, kSelectionExtendToEndOfLine, kSelectionExtendToEndOfParagraph, kSelectionExtendToEndOfColumn, kSelectionExtendToEndOfDocument, kSelectionExtendPageDown };
if(first < last && towardBegin.find(unit) != towardBegin.end())
std::swap(first, last);
else if(last < first && towardEnd.find(unit) != towardEnd.end())
std::swap(first, last);
}
if(range.freehanded || range.columnar)
{
if(unit == kSelectionExtendLeft)
unit = kSelectionExtendFreehandedLeft;
else if(unit == kSelectionExtendRight)
unit = kSelectionExtendFreehandedRight;
}
switch(unit)
{
case kSelectionExtendLeft: return range_t(first, move(buffer, last, kSelectionMoveLeft, layout), range.columnar, range.freehanded);
case kSelectionExtendRight: return range_t(first, move(buffer, last, kSelectionMoveRight, layout), range.columnar, range.freehanded);
case kSelectionExtendFreehandedLeft: return range_t(first, move(buffer, last, kSelectionMoveFreehandedLeft, layout), range.columnar, range.freehanded);
case kSelectionExtendFreehandedRight: return range_t(first, move(buffer, last, kSelectionMoveFreehandedRight, layout), range.columnar, range.freehanded);
case kSelectionExtendUp: return range_t(first, move(buffer, last, kSelectionMoveUp, layout), range.columnar, range.freehanded);
case kSelectionExtendDown: return range_t(first, move(buffer, last, kSelectionMoveDown, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfWord: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfWord, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfWord: return range_t(first, move(buffer, last, kSelectionMoveToEndOfWord, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfSubWord: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfSubWord, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfSubWord: return range_t(first, move(buffer, last, kSelectionMoveToEndOfSubWord, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfIndentedLine: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfIndentedLine, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfIndentedLine: return range_t(first, move(buffer, last, kSelectionMoveToEndOfIndentedLine, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfSoftLine: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfSoftLine, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfSoftLine: return range_t(first, move(buffer, last, kSelectionMoveToEndOfSoftLine, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfLine: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfLine, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfLine: return range_t(first, move(buffer, last, kSelectionMoveToEndOfLine, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfParagraph: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfParagraph, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfParagraph: return range_t(first, move(buffer, last, kSelectionMoveToEndOfParagraph, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfTypingPair: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfTypingPair, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfTypingPair: return range_t(first, move(buffer, last, kSelectionMoveToEndOfTypingPair, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfColumn: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfColumn, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfColumn: return range_t(first, move(buffer, last, kSelectionMoveToEndOfColumn, layout), range.columnar, range.freehanded);
case kSelectionExtendToBeginOfDocument: return range_t(first, move(buffer, last, kSelectionMoveToBeginOfDocument, layout), range.columnar, range.freehanded);
case kSelectionExtendToEndOfDocument: return range_t(first, move(buffer, last, kSelectionMoveToEndOfDocument, layout), range.columnar, range.freehanded);
case kSelectionExtendPageUp: return range_t(first, move(buffer, last, kSelectionMovePageUp, layout), range.columnar, range.freehanded);
case kSelectionExtendPageDown: return range_t(first, move(buffer, last, kSelectionMovePageDown, layout), range.columnar, range.freehanded);
case kSelectionExtendToSoftLine: return range_t(move(buffer, min, kSelectionMoveToBeginOfSoftLine, layout), min.index != max.index && max.index == move(buffer, max, kSelectionMoveToBeginOfSoftLine, layout).index ? max : move(buffer, move(buffer, max, kSelectionMoveToEndOfSoftLine, layout), kSelectionMoveRight, layout), false, false, true);
case kSelectionExtendToLine: return range_t(move(buffer, min, kSelectionMoveToBeginOfLine, layout), min.index != max.index && buffer.convert(max.index).column == 0 ? max : move(buffer, move(buffer, max, kSelectionMoveToEndOfLine, layout), kSelectionMoveRight, layout), false, false, true);
case kSelectionExtendToLineExclLF: return range_t(move(buffer, min, kSelectionMoveToBeginOfLine, layout), min.index != max.index && buffer.convert(max.index).column == 0 ? max : move(buffer, max, kSelectionMoveToEndOfLine, layout), false, false, true);
case kSelectionExtendToParagraph: return range_t(move(buffer, min, kSelectionMoveToBeginOfHardParagraph, layout), move(buffer, max, kSelectionMoveToEndOfHardParagraph, layout), false, false, true);
case kSelectionExtendToAll: return range_t(move(buffer, min, kSelectionMoveToBeginOfDocument, layout), move(buffer, max, kSelectionMoveToEndOfDocument, layout), false, false, true);
case kSelectionExtendToWord:
{
size_t from = min.index, to = max.index;
size_t bol = buffer.begin(buffer.convert(from).line);
size_t eol = buffer.eol(buffer.convert(to).line);
std::string outerLeftType = from == bol ? kCharacterClassUnknown : character_class(buffer, from-1);
std::string innerLeftType = from == eol ? kCharacterClassUnknown : character_class(buffer, from);
std::string innerRightType = to == bol ? kCharacterClassUnknown : character_class(buffer, to-1);
std::string outerRightType = to == eol ? kCharacterClassUnknown : character_class(buffer, to);
bool extendLeft = false, extendRight = false;
if(from == to) // no existing selection
{
if(outerLeftType == outerRightType)
extendLeft = extendRight = true;
else if(outerRightType == kCharacterClassWord || outerLeftType == kCharacterClassSpace || outerLeftType == kCharacterClassUnknown || (outerLeftType == kCharacterClassOther && outerRightType != kCharacterClassSpace && outerRightType != kCharacterClassUnknown))
extendRight = true;
else
extendLeft = true;
}
else
{
if(outerLeftType == innerLeftType && innerRightType != outerRightType)
extendLeft = true;
else if(outerLeftType != innerLeftType && outerRightType == innerRightType)
extendRight = true;
else if(outerLeftType == innerLeftType && outerRightType == innerRightType)
extendLeft = extendRight = true;
}
if(extendLeft)
{
while(bol < from && character_class(buffer, from-1) == outerLeftType)
from -= buffer[from-1].size();
}
if(extendRight)
{
while(to < eol && character_class(buffer, to) == outerRightType)
to += buffer[to].size();
}
return range_t(from, to, range.columnar, false, true);
}
break;
case kSelectionExtendToTypingPair:
{
size_t from = begin_of_typing_pair(buffer, min.index, true);
size_t to = end_of_typing_pair(buffer, max.index, true);
if(from != min.index && to != max.index)
{
size_t firstLine = buffer.convert(from).line, lastLine = buffer.convert(to).line;
size_t bol = buffer.begin(firstLine), eol = buffer.end(lastLine);
return firstLine != lastLine && all_whitespace(buffer, bol, from) && all_whitespace(buffer, to, eol) ? range_t(bol, eol, false, false, true) : range_t(from, to, false, false, true);
}
}
break;
case kSelectionExtendToScope:
{
size_t from = min.index, to = max.index;
scope::scope_t leftScope = buffer.scope(from, false).left;
scope::scope_t rightScope = buffer.scope(to, false).right;
if(leftScope == rightScope)
{
// D(DBF_TextView_Internal, bug("select both sides: %s\n", to_s(leftScope).c_str()););
from = extend_scope_left(buffer, from, leftScope);
to = extend_scope_right(buffer, to, rightScope);
}
else if(from != to)
{
scope::scope_t innerLeftScope = buffer.scope(from, false).right;
scope::scope_t innerRightScope = buffer.scope(to, false).left;
// D(DBF_TextView_Internal, bug("%s\n", to_s(leftScope).c_str()););
// D(DBF_TextView_Internal, bug("%s\n", to_s(innerLeftScope).c_str()););
// D(DBF_TextView_Internal, bug("%s\n", to_s(innerRightScope).c_str()););
// D(DBF_TextView_Internal, bug("%s\n", to_s(rightScope).c_str()););
scope::scope_t scope = shared_prefix(shared_prefix(leftScope, innerLeftScope), shared_prefix(rightScope, innerRightScope));
// D(DBF_TextView_Internal, bug("→ %s\n", to_s(scope).c_str()););
from = extend_scope_left(buffer, from, scope);
to = extend_scope_right(buffer, to, scope);
}
else if(leftScope.has_prefix(rightScope))
{
while(leftScope.parent() != rightScope)
leftScope = leftScope.parent();
// D(DBF_TextView_Internal, bug("select left side: %s\n", to_s(leftScope).c_str()););
from = extend_scope_left(buffer, from, leftScope);
}
else if(rightScope.has_prefix(leftScope))
{
while(rightScope.parent() != leftScope)
rightScope = rightScope.parent();
// D(DBF_TextView_Internal, bug("select right side: %s\n", to_s(rightScope).c_str()););
to = extend_scope_right(buffer, to, rightScope);
}
else if(from == to && buffer.convert(to).column == 0)
{
to = extend_scope_right(buffer, to, rightScope);
}
else
{
// D(DBF_TextView_Internal, bug("intersection: %s\n != %s\n", to_s(leftScope).c_str(), to_s(rightScope).c_str()););
scope::scope_t const& scope = shared_prefix(leftScope, rightScope);
// D(DBF_TextView_Internal, bug("→ %s\n", to_s(scope).c_str()););
from = extend_scope_left(buffer, from, scope);
to = extend_scope_right(buffer, to, scope);
}
return range_t(from, to, false, false, true);
}
break;
}
return range;
}
ranges_t extend (buffer_t const& buffer, ranges_t const& selection, select_unit_type const unit, layout_movement_t const* layout)
{
ranges_t res;
citerate(range, (unit == kSelectionExtendToWord || unit == kSelectionExtendToTypingPair || unit == kSelectionExtendToScope) ? dissect_columnar(buffer, selection) : selection)
res.push_back(extend(buffer, *range, unit, layout));
return sanitize(buffer, res);
}
ranges_t extend_if_empty (buffer_t const& buffer, ranges_t const& selection, select_unit_type const unit, layout_movement_t const* layout)
{
ranges_t res;
iterate(range, selection)
{
if(not_empty(buffer, *range))
{
res.push_back(*range);
}
else
{
citerate(r, dissect_columnar(buffer, *range))
{
range_t range = extend(buffer, *r, unit, layout);
if(unit == kSelectionExtendToEndOfParagraph && range.empty())
range = extend(buffer, range, kSelectionExtendRight, layout);
res.push_back(range);
}
}
}
return sanitize(buffer, res);
}
// ================
// = Select Scope =
// ================
static size_t extend_scope_left (buffer_t const& buffer, size_t caret, scope::selector_t const& scopeSelector)
{
while(caret && scopeSelector.does_match(buffer.scope(caret).left))
caret -= buffer[caret-1].size();
return caret;
}
static size_t extend_scope_right (buffer_t const& buffer, size_t caret, scope::selector_t const& scopeSelector)
{
while(caret < buffer.size() && scopeSelector.does_match(buffer.scope(caret).right))
caret += buffer[caret].size();
return caret;
}
static range_t select_scope (buffer_t const& buffer, range_t const& range, scope::selector_t const& scopeSelector)
{
return range_t(extend_scope_left(buffer, range.min().index, scopeSelector), extend_scope_right(buffer, range.max().index, scopeSelector));
}
ranges_t select_scope (buffer_t const& buffer, ranges_t const& selection, scope::selector_t const& scopeSelector)
{
ranges_t res;
iterate(range, selection)
res.push_back(select_scope(buffer, *range, scopeSelector));
return sanitize(buffer, res);
}
// ================
// = Obtain Scope =
// ================
scope::context_t scope (buffer_t const& buffer, ranges_t const& selection, std::string const& extraAttributes)
{
scope::context_t res;
if(selection.size() == 1)
{
range_t const range = selection.last();
if(range.empty())
res = buffer.scope(range.last.index);
else res = shared_prefix(buffer.scope(range.min().index).right, buffer.scope(range.max().index).left);
}
else
{
scope::scope_t scope;
citerate(range, selection)
{
scope::scope_t newScope = shared_prefix(buffer.scope(range->min().index).right, buffer.scope(range->max().index).left);
scope = scope ? shared_prefix(scope, newScope) : newScope;
}
res = scope;
}
if(extraAttributes != NULL_STR)
{
citerate(atom, text::tokenize(extraAttributes.begin(), extraAttributes.end(), ' '))
{
res.left = res.left.append(*atom);
res.right = res.right.append(*atom);
}
}
if(selection.size() > 1)
{
res.left = res.left.append("dyn.caret.mixed");
res.right = res.right.append("dyn.caret.mixed");
}
else if(selection.last().columnar)
{
res.left = res.left.append("dyn.caret.mixed.columnar");
res.right = res.right.append("dyn.caret.mixed.columnar");
}
else
{
size_t const leftCaret = selection.last().min().index;
size_t const rightCaret = selection.last().max().index;
if(leftCaret == 0)
res.left = res.left.append("dyn.caret.begin.document");
else if(leftCaret == buffer.begin(buffer.convert(leftCaret).line))
res.left = res.left.append("dyn.caret.begin.line");
if(rightCaret == buffer.size())
res.right = res.right.append("dyn.caret.end.document");
else if(rightCaret == buffer.eol(buffer.convert(rightCaret).line))
res.right = res.right.append("dyn.caret.end.line");
}
if(not_empty(buffer, selection))
{
res.left = res.left.append("dyn.selection");
res.right = res.right.append("dyn.selection");
}
return res;
}
// ====================
// = Highlight Ranges =
// ====================
ranges_t highlight_ranges_for_movement (buffer_t const& buffer, ranges_t const& oldSelection, ranges_t const& newSelection)
{
ranges_t res;
if(oldSelection.size() != newSelection.size())
return res;
for(auto oldRange = oldSelection.begin(), newRange = newSelection.begin(); oldRange != oldSelection.end(); ++oldRange, ++newRange)
{
if(*oldRange == *newRange || !oldRange->empty() || !newRange->empty())
continue;
size_t prevCaret = oldRange->last.index;
size_t curCaret = newRange->last.index;
auto pairs = character_pairs(buffer.scope(std::min(prevCaret, curCaret)), "highlightPairs");
if(prevCaret < curCaret && prevCaret + buffer[prevCaret].size() == curCaret) // moved right, check end character
{
auto pair = first_match(buffer, prevCaret, pairs);
if(pair && !pair.matched_first)
{
size_t from = begin_of_typing_pair(buffer, prevCaret, true);
if(from != prevCaret && does_match(buffer, from, pair.first))
res.push_back(range_t(from, from + pair.first.size()));
}
}
else if(curCaret < prevCaret && curCaret + buffer[curCaret].size() == prevCaret) // moved left, check begin character
{
auto pair = first_match(buffer, curCaret, pairs);
if(pair && pair.matched_first)
{
size_t to = end_of_typing_pair(buffer, prevCaret, true);
if(to != prevCaret && to >= pair.second.size() && does_match(buffer, to - pair.second.size(), pair.second))
res.push_back(range_t(to - pair.second.size(), to));
}
}
}
return res;
}
// ========
// = Find =
// ========
static bool is_subset (range_t const& range, ranges_t const& ranges)
{
iterate(r, ranges)
{
if(r->min() <= range.min() && range.max() <= r->max())
return true;
}
return ranges.empty();
}
std::map< range_t, std::map<std::string, std::string> > find_all (buffer_t const& buffer, std::string const& searchFor, find::options_t options, ranges_t const& searchRanges)
{
std::map< range_t, std::map<std::string, std::string> > res;
if(searchFor == NULL_STR || searchFor == "")
return res;
ranges_t ranges = dissect_columnar(buffer, searchRanges);
find::find_t f(searchFor, (find::options_t)(options & ~find::backwards));
ssize_t total = 0;
iterate(memory, buffer)
{
char const* buf = (*memory).data();
size_t len = (*memory).size();
for(ssize_t offset = 0; offset < len; )
{
std::map<std::string, std::string> captures;
std::pair<ssize_t, ssize_t> const& m = f.match(buf + offset, len - offset, &captures);
if(m.first <= m.second)
{
range_t r(total + offset + m.first, total + offset + m.second, false, false, true);
if(is_subset(r, ranges))
res.insert(std::make_pair(r, captures));
}
ASSERT_NE(m.second, 0); ASSERT_LE(m.second, len - offset);
offset += m.second;
}
total += len;
}
std::map<std::string, std::string> captures;
std::pair<ssize_t, ssize_t> m = f.match(NULL, 0, &captures);
while(m.first <= m.second)
{
range_t r(total + m.first, total + m.second, false, false, true);
if(is_subset(r, ranges))
res.insert(std::make_pair(r, captures));
captures.clear();
m = f.match(NULL, 0, &captures);
}
return res;
}
static std::map< range_t, std::map<std::string, std::string> > regexp_find (buffer_t const& buffer, std::string const& searchFor, find::options_t options, size_t first, size_t last)
{
std::map< range_t, std::map<std::string, std::string> > res;
OnigOptionType ptrnOptions = ONIG_OPTION_NONE;
if(options && find::ignore_case)
ptrnOptions |= ONIG_OPTION_IGNORECASE;
std::string str = buffer.substr(0, buffer.size());
if(regexp::match_t const& m = search(regexp::pattern_t(searchFor, ptrnOptions), str.data(), str.data() + str.size(), str.data() + first, str.data() + last))
res.insert(std::make_pair(ng::range_t(m.begin(), m.end()), m.captures()));
return res;
}
std::map< range_t, std::map<std::string, std::string> > find (buffer_t const& buffer, ranges_t const& selection, std::string const& searchFor, find::options_t options, ranges_t const& searchRanges)
{
std::map< range_t, std::map<std::string, std::string> > res;
if(searchFor == NULL_STR || searchFor == "")
return res;
if(selection.size() == 1 && (options & (find::regular_expression|find::wrap_around|find::all_matches|find::extend_selection)) == find::regular_expression)
{
size_t first = (options & find::backwards) ? selection.last().min().index : selection.last().max().index;
size_t last = (options & find::backwards) ? 0 : buffer.size();
return regexp_find(buffer, searchFor, options, first, last);
}
auto tmp = find_all(buffer, searchFor, options, searchRanges);
if(options & find::all_matches)
return tmp;
if(options & find::extend_selection)
{
ng::index_t anchor(options & find::backwards ? buffer.size() : 0);
iterate(range, selection)
{
anchor = options & find::backwards ? std::min(range->min(), anchor) : std::max(range->max(), anchor);
res.insert(std::make_pair(*range, std::map<std::string, std::string>()));
}
if(options & find::backwards)
{
auto it = tmp.lower_bound(anchor);
if(it != tmp.begin())
res.insert(*--it);
}
else
{
auto it = tmp.upper_bound(anchor);
if(it != tmp.end())
res.insert(*it);
}
return res;
}
citerate(range, dissect_columnar(buffer, selection))
{
if(options & find::backwards)
{
auto it = tmp.lower_bound(range->min());
if(it == tmp.begin())
{
if(!(options & find::wrap_around) || tmp.empty())
continue;
it = --tmp.end();
}
else
{
--it;
}
if(range->sorted() != it->first.sorted())
res.insert(*it);
}
else
{
auto it = tmp.upper_bound(range->max());
if(it == tmp.end() && (options & find::wrap_around))
it = tmp.begin();
if(it != tmp.end() && range->sorted() != it->first.sorted())
res.insert(*it);
}
}
return res;
}
// =============
// = All Words =
// =============
ranges_t all_words (buffer_t const& buffer)
{
ranges_t res;
std::string charType = kCharacterClassOther;
size_t from = 0;
for(size_t i = 0; i < buffer.size(); )
{
std::string newCharType = character_class(buffer, i);
if(charType != newCharType)
{
if(charType != kCharacterClassSpace && charType != kCharacterClassOther && from != i)
res.push_back(range_t(from, i));
charType = newCharType;
from = i;
}
i += buffer[i].size();
}
if(charType != kCharacterClassSpace && charType != kCharacterClassOther && from != buffer.size())
res.push_back(range_t(from, buffer.size()));
return res;
}
// ==================
// = to/from string =
// ==================
ranges_t from_string (buffer_t const& buffer, std::string const& str)
{
ranges_t res;
citerate(range, text::selection_t(str))
res.push_back(range_t(index_t(buffer.convert(range->from), range->from.offset), index_t(buffer.convert(range->to), range->to.offset), range->columnar, range->from.offset || range->to.offset));
return res;
}
std::string to_s (buffer_t const& buffer, ranges_t const& ranges)
{
text::selection_t res;
iterate(range, ranges)
{
text::pos_t from = buffer.convert(range->first.index), to = buffer.convert(range->last.index);
from.offset = range->freehanded ? range->first.carry : 0;
to.offset = range->freehanded ? range->last.carry : 0;
res.push_back(text::range_t(from, to, range->columnar));
}
return res;
}
} /* ng */