Handle triple click selection

Double-clicking selects the word underneath it on OS X and Linux text
editors. Triple-clicking selects the whole line.

Selected line can be copied or commented on.

Feature: Issue 5375
Change-Id: I79fff57e7dbfde18ab741a3a67b61869fb52cecf
This commit is contained in:
Viktar Donich
2017-02-06 10:47:53 -08:00
parent 4a74548c22
commit cb2d12770d
2 changed files with 119 additions and 27 deletions

View File

@@ -94,13 +94,52 @@
}
},
_normalizeRange: function(range) {
range = GrRangeNormalizer.normalize(range);
return {
start: this._normalizeSelectionSide(range.startContainer,
range.startOffset),
end: this._normalizeSelectionSide(range.endContainer, range.endOffset),
};
_normalizeRange: function(domRange) {
var range = GrRangeNormalizer.normalize(domRange);
return this._fixTripleClickSelection({
start: this._normalizeSelectionSide(
range.startContainer, range.startOffset),
end: this._normalizeSelectionSide(
range.endContainer, range.endOffset),
}, domRange);
},
/**
* Adjust triple click selection for the whole line.
* domRange.endContainer may be one of the following:
* 1) 0 offset at right column's line number cell, or
* 2) 0 offset at left column's line number at the next line.
* Case 1 means left column was triple clicked.
* Case 2 means right column or unified view triple clicked.
* @param {!Object} range Normalized range, ie column/line numbers
* @param {!Range} domRange DOM Range object
* @return {!Object} fixed normalized range
*/
_fixTripleClickSelection: function(range, domRange) {
var start = range.start;
var end = range.end;
var endsAtOtherSideLineNum =
domRange.endOffset === 0 &&
domRange.endContainer.nodeName === 'TD' &&
(domRange.endContainer.classList.contains('left') ||
domRange.endContainer.classList.contains('right'));
var endsOnOtherSideStart = endsAtOtherSideLineNum ||
end &&
end.column === 0 &&
end.line === start.line &&
end.side != start.side;
if (endsOnOtherSideStart || endsAtOtherSideLineNum) {
// Selection ends at the beginning of the next line.
// Move the selection to the end of the previous line.
range.end = {
node: start.node,
column: this._getLength(
domRange.cloneContents().querySelector('.contentText')),
side: start.side,
line: start.line,
};
}
return range;
},
/**

View File

@@ -26,35 +26,42 @@ limitations under the License.
<test-fixture id="basic">
<template>
<style>
.tab-indicator:before {
color: #C62828;
/* >> character */
content: '\00BB';
}
</style>
<gr-diff-highlight>
<table id="diffTable">
<tbody class="section both">
<tr class="diff-row side-by-side" left-type="both" right-type="both">
<td class="left lineNum" data-value="138">138</td>
<td class="left lineNum" data-value="138"></td>
<td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
<td class="right lineNum" data-value="119">119</td>
<td class="right lineNum" data-value="119"></td>
<td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
</tr>
</tbody>
<tbody class="section delta">
<tr class="diff-row side-by-side" left-type="remove" right-type="add">
<td class="left lineNum" data-value="140">140</td>
<td class="left lineNum" data-value="140"></td>
<!-- Next tag is formatted to eliminate zero-length text nodes. -->
<td class="content remove"><div class="contentText">na💢ti <hl class="foo">te, inquit</hl>, sumus <hl class="bar">aliquando</hl> otiosum, <hl>certe</hl> a <hl><span class="tab withIndicator" style="tab-size:8;"></span></hl>udiam, <hl>quid</hl> sit, <span class="tab withIndicator" style="tab-size:8;"></span>quod <hl>Epicurum</hl></div><gr-diff-comment-thread>
<td class="content remove"><div class="contentText">na💢ti <hl class="foo">te, inquit</hl>, sumus <hl class="bar">aliquando</hl> otiosum, <hl>certe</hl> a <hl><span class="tab-indicator" style="tab-size:8;"> </span></hl>udiam, <hl>quid</hl> sit, <span class="tab-indicator" style="tab-size:8;"> </span>quod <hl>Epicurum</hl></div><gr-diff-comment-thread>
[Yet another random diff thread content here]
</gr-diff-comment-thread></td>
<td class="right lineNum" data-value="120">120</td>
<td class="right lineNum" data-value="120"></td>
<!-- Next tag is formatted to eliminate zero-length text nodes. -->
<td class="content add"><div class="contentText">nacti , <hl>,</hl> sumus <hl><span class="tab withIndicator" style="tab-size:8;"></span></hl> otiosum, <span class="tab withIndicator" style="tab-size:8;"></span> audiam, sit, quod</div></td>
<td class="content add"><div class="contentText">nacti , <hl>,</hl> sumus <hl><span class="tab-indicator" style="tab-size:8;"> </span></hl> otiosum, <span class="tab-indicator" style="tab-size:8;"> </span> audiam, sit, quod</div></td>
</tr>
</tbody>
<tbody class="section both">
<tr class="diff-row side-by-side" left-type="both" right-type="both">
<td class="left lineNum" data-value="141"></td>
<td class="content both"><div class="contentText">nam et<hl><span class="tab withIndicator" style="tab-size:8;"> </span></hl>complectitur<span class="tab withIndicator" style="tab-size:8;"> </span>verbis, quod vult, et dicit plane, quod intellegam;</div></td>
<td class="content both"><div class="contentText">nam et<hl><span class="tab-indicator" style="tab-size:8;"> </span></hl>complectitur<span class="tab-indicator" style="tab-size:8;"> </span>verbis, quod vult, et dicit plane, quod intellegam;</div></td>
<td class="right lineNum" data-value="130"></td>
<td class="content both"><div class="contentText">nam et complectitur verbis, quod vult, et dicit plane, quod intellegam;</div></td>
</tr>
@@ -95,7 +102,7 @@ limitations under the License.
<td class="left lineNum" data-value="165"></td>
<td class="content both"><div class="contentText">in physicis, quibus maxime gloriatur, primum totus est alienus. Democritea dicit</div></td>
<td class="right lineNum" data-value="147"></td>
<td class="content both"><div class="contentText">in physicis, <hl><span class="tab withIndicator" style="tab-size:8;"> </span></hl> quibus maxime gloriatur, primum totus est alienus. Democritea dicit</div></td>
<td class="content both"><div class="contentText">in physicis, <hl><span class="tab-indicator" style="tab-size:8;"> </span></hl> quibus maxime gloriatur, primum totus est alienus. Democritea dicit</div></td>
</tr>
</tbody>
@@ -121,7 +128,7 @@ limitations under the License.
setup(function() {
sandbox = sinon.sandbox.create();
element = fixture('basic');
element = fixture('basic')[1];
});
teardown(function() {
@@ -290,7 +297,7 @@ limitations under the License.
startLine: 119,
startChar: 10,
endLine: 120,
endChar: 34,
endChar: 36,
});
assert.equal(getActionSide(), 'right');
});
@@ -352,7 +359,7 @@ limitations under the License.
startLine: 140,
startChar: 2,
endLine: 140,
endChar: 60,
endChar: 61,
});
assert.equal(getActionSide(), 'left');
});
@@ -361,7 +368,7 @@ limitations under the License.
var contentText = stubContent(140, 'left');
var contentTd = contentText.parentElement;
emulateSelection(contentTd.previousElementSibling.firstChild, 2,
emulateSelection(contentTd.previousElementSibling, 0,
contentText.firstChild, 2);
assert.isFalse(element.isRangeSelected());
});
@@ -389,7 +396,7 @@ limitations under the License.
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
startLine: 140,
startChar: 81,
startChar: 83,
endLine: 141,
endChar: 4,
});
@@ -406,13 +413,14 @@ limitations under the License.
startLine: 140,
startChar: 4,
endLine: 140,
endChar: 81,
endChar: 83,
});
assert.equal(getActionSide(), 'left');
});
test('starts in context element', function() {
var contextControl = diff.querySelector('.contextControl');
var contextControl =
diff.querySelector('.contextControl').querySelector('gr-button');
var content = stubContent(146, 'right');
emulateSelection(contextControl, 0, content.firstChild, 7);
// TODO (viktard): Select nearest line.
@@ -420,9 +428,10 @@ limitations under the License.
});
test('ends in context element', function() {
var contextControl = diff.querySelector('.contextControl');
var contextControl =
diff.querySelector('.contextControl').querySelector('gr-button');
var content = stubContent(141, 'left');
emulateSelection(content.firstChild, 2, contextControl, 0);
emulateSelection(content.firstChild, 2, contextControl, 1);
// TODO (viktard): Select nearest line.
assert.isFalse(element.isRangeSelected());
});
@@ -459,13 +468,13 @@ limitations under the License.
var content = stubContent(140, 'left');
emulateSelection(
content.querySelectorAll('hl')[3], 0,
content.querySelectorAll('span')[1], 0);
content.querySelectorAll('span')[1].nextSibling, 1);
assert.isTrue(element.isRangeSelected());
assert.deepEqual(getActionRange(), {
startLine: 140,
startChar: 51,
endLine: 140,
endChar: 68,
endChar: 71,
});
assert.equal(getActionSide(), 'left');
});
@@ -485,7 +494,7 @@ limitations under the License.
var content = stubContent(140, 'left');
var child = content.lastChild.lastChild;
var result = GrRangeNormalizer._getTextOffset(content, child);
assert.equal(result, 73);
assert.equal(result, 75);
content = stubContent(146, 'right');
child = content.lastChild;
result = GrRangeNormalizer._getTextOffset(content, child);
@@ -496,6 +505,50 @@ limitations under the License.
// TODO (viktard): Empty lines in selection end.
// TODO (viktard): Only empty lines selected.
// TODO (viktard): Unified mode.
suite('triple click', function() {
test('_fixTripleClickSelection', function() {
var fakeRange = {
startContainer: '',
startOffset: '',
endContainer: '',
endOffset: ''
};
var fixedRange = {};
sandbox.stub(GrRangeNormalizer, 'normalize').returns(fakeRange);
sandbox.stub(element, '_normalizeSelectionSide');
sandbox.stub(element, '_fixTripleClickSelection').returns(fixedRange);
assert.strictEqual(element._normalizeRange({}), fixedRange);
assert.isTrue(element._fixTripleClickSelection.called);
});
test('left pane', function() {
var startNode = stubContent(138, 'left');
var endNode =
stubContent(119, 'right').parentElement.previousElementSibling;
builder.getLineNumberByChild.withArgs(endNode).returns(119);
emulateSelection(startNode, 0, endNode, 0);
assert.deepEqual(getActionRange(), {
startLine: 138,
startChar: 0,
endLine: 138,
endChar: 63,
});
});
test('right pane', function() {
var startNode = stubContent(119, 'right');
var endNode =
stubContent(140, 'left').parentElement.previousElementSibling;
emulateSelection(startNode, 0, endNode, 0);
assert.deepEqual(getActionRange(), {
startLine: 119,
startChar: 0,
endLine: 119,
endChar: 63,
});
});
});
});
});
</script>