plugin: extend double-click selection to '' "" `` quote pairs
This commit is contained in:
parent
376a190b52
commit
03f209bfd7
|
|
@ -185,11 +185,16 @@ Therefore the palette is replaced, not extended, and the replacement is built on
|
|||
- Double-clicking next to a bracket (`()[]{}`) selects the text strictly between
|
||||
the matching pair, **multi-line aware** and nesting-aware. Adjacency is checked
|
||||
on both sides of the click; openers select forward, closers select backward.
|
||||
- Double-clicking next to a quote (`''`, `""`, `` `` ``) selects the text strictly
|
||||
between the enclosing pair. Quotes are symmetric and non-nesting, so matching is
|
||||
**line-local**: direction is decided by the parity of same-kind quotes preceding
|
||||
the clicked one (even → opener, scan forward; odd → closer, scan backward), via
|
||||
`isQuote` / `quoteIsOpener` / `findQuoteForward` / `findQuoteBackward`.
|
||||
- Implemented in the event filter via `selectBracketPairAt`: maps the click to a
|
||||
document cursor (`coordinatesToCursor`), reads neighbouring characters
|
||||
(`characterAt`), scans with depth counting across line ends, and sets the
|
||||
selection. When no bracket is adjacent it falls through to Kate's default
|
||||
double-click (word select).
|
||||
selection. When no bracket or quote is adjacent it falls through to Kate's
|
||||
default double-click (word select).
|
||||
- A left double-click leaves the button held, so it also arms the chord anchor:
|
||||
the user can go straight from a double-click (bracket- or word-select) into a
|
||||
left+middle / left+right chord without releasing.
|
||||
|
|
|
|||
|
|
@ -89,6 +89,16 @@ QChar matchingBracket(QChar c, bool &isOpener)
|
|||
}
|
||||
}
|
||||
|
||||
// Quote delimiters understood by the double-click selection feature. Unlike
|
||||
// brackets these are symmetric (same opening and closing char) and do not nest,
|
||||
// so matching is handled separately: scan the click's line for the enclosing
|
||||
// pair of identical quote chars.
|
||||
bool isQuote(QChar c)
|
||||
{
|
||||
const ushort u = c.unicode();
|
||||
return u == '\'' || u == '"' || u == '`';
|
||||
}
|
||||
|
||||
// Advance a cursor by one character, wrapping across line ends. Returns false
|
||||
// at end of document.
|
||||
bool stepForward(KTextEditor::Document *doc, KTextEditor::Cursor &c)
|
||||
|
|
@ -164,6 +174,45 @@ KTextEditor::Cursor findOpener(KTextEditor::Document *doc, KTextEditor::Cursor p
|
|||
return KTextEditor::Cursor::invalid();
|
||||
}
|
||||
|
||||
// Count occurrences of \a q on \a line in columns [0, col). An even count means
|
||||
// the quote at \a col opens a pair; odd means it closes one. Quotes do not nest,
|
||||
// so parity of preceding quotes decides direction.
|
||||
bool quoteIsOpener(KTextEditor::Document *doc, int line, int col, QChar q)
|
||||
{
|
||||
int count = 0;
|
||||
for (int c = 0; c < col; ++c) {
|
||||
if (doc->characterAt(KTextEditor::Cursor(line, c)) == q) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return (count % 2) == 0;
|
||||
}
|
||||
|
||||
// Find the matching quote char of the same kind on \a line, searching columns
|
||||
// (col, lineLength) forward. Returns the closer's cursor, or invalid if none.
|
||||
KTextEditor::Cursor findQuoteForward(KTextEditor::Document *doc, int line, int col, QChar q)
|
||||
{
|
||||
const int len = doc->lineLength(line);
|
||||
for (int c = col + 1; c < len; ++c) {
|
||||
if (doc->characterAt(KTextEditor::Cursor(line, c)) == q) {
|
||||
return KTextEditor::Cursor(line, c);
|
||||
}
|
||||
}
|
||||
return KTextEditor::Cursor::invalid();
|
||||
}
|
||||
|
||||
// Find the matching quote char of the same kind on \a line, searching columns
|
||||
// [0, col) backward. Returns the opener's cursor, or invalid if none.
|
||||
KTextEditor::Cursor findQuoteBackward(KTextEditor::Document *doc, int line, int col, QChar q)
|
||||
{
|
||||
for (int c = col - 1; c >= 0; --c) {
|
||||
if (doc->characterAt(KTextEditor::Cursor(line, c)) == q) {
|
||||
return KTextEditor::Cursor(line, c);
|
||||
}
|
||||
}
|
||||
return KTextEditor::Cursor::invalid();
|
||||
}
|
||||
|
||||
// Prefix marking a palette id that is a ":" command string to execute rather
|
||||
// than a QAction to trigger.
|
||||
const QLatin1String kCmdPrefix("ollie:cmd:");
|
||||
|
|
@ -1167,6 +1216,40 @@ bool OllieView::selectBracketPairAt(KTextEditor::View *view, const QPoint &globa
|
|||
const bool haveLeft = stepBackward(doc, leftCur);
|
||||
const QChar leftCh = haveLeft ? doc->characterAt(leftCur) : QChar();
|
||||
|
||||
// Quote pairs ('' "" ``) are symmetric and non-nesting: resolve them on the
|
||||
// click's line and select the content between the enclosing pair. Handled
|
||||
// before brackets since the two delimiter sets are disjoint.
|
||||
{
|
||||
QChar q;
|
||||
int qCol = -1;
|
||||
if (isQuote(rightCh)) {
|
||||
q = rightCh;
|
||||
qCol = at.column();
|
||||
} else if (haveLeft && isQuote(leftCh)) {
|
||||
q = leftCh;
|
||||
qCol = leftCur.column();
|
||||
}
|
||||
if (!q.isNull()) {
|
||||
const int line = at.line();
|
||||
KTextEditor::Cursor qOpen = KTextEditor::Cursor::invalid();
|
||||
KTextEditor::Cursor qClose = KTextEditor::Cursor::invalid();
|
||||
if (quoteIsOpener(doc, line, qCol, q)) {
|
||||
qOpen = KTextEditor::Cursor(line, qCol);
|
||||
qClose = findQuoteForward(doc, line, qCol, q);
|
||||
} else {
|
||||
qClose = KTextEditor::Cursor(line, qCol);
|
||||
qOpen = findQuoteBackward(doc, line, qCol, q);
|
||||
}
|
||||
if (!qOpen.isValid() || !qClose.isValid()) {
|
||||
return false; // unbalanced quote on this line
|
||||
}
|
||||
const KTextEditor::Cursor innerStart(line, qOpen.column() + 1);
|
||||
view->setSelection(KTextEditor::Range(innerStart, qClose));
|
||||
view->setCursorPosition(qClose);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool rOpener = false;
|
||||
const QChar rMatch = matchingBracket(rightCh, rOpener);
|
||||
bool lOpener = false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue