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:
@@ -94,13 +94,52 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_normalizeRange: function(range) {
|
_normalizeRange: function(domRange) {
|
||||||
range = GrRangeNormalizer.normalize(range);
|
var range = GrRangeNormalizer.normalize(domRange);
|
||||||
return {
|
return this._fixTripleClickSelection({
|
||||||
start: this._normalizeSelectionSide(range.startContainer,
|
start: this._normalizeSelectionSide(
|
||||||
range.startOffset),
|
range.startContainer, range.startOffset),
|
||||||
end: this._normalizeSelectionSide(range.endContainer, range.endOffset),
|
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;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -26,35 +26,42 @@ limitations under the License.
|
|||||||
|
|
||||||
<test-fixture id="basic">
|
<test-fixture id="basic">
|
||||||
<template>
|
<template>
|
||||||
|
<style>
|
||||||
|
.tab-indicator:before {
|
||||||
|
color: #C62828;
|
||||||
|
/* >> character */
|
||||||
|
content: '\00BB';
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<gr-diff-highlight>
|
<gr-diff-highlight>
|
||||||
<table id="diffTable">
|
<table id="diffTable">
|
||||||
|
|
||||||
<tbody class="section both">
|
<tbody class="section both">
|
||||||
<tr class="diff-row side-by-side" left-type="both" right-type="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="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>
|
<td class="content both"><div class="contentText">[14] Nam cum ad me in Cumanum salutandi causa uterque venisset,</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
<tbody class="section delta">
|
<tbody class="section delta">
|
||||||
<tr class="diff-row side-by-side" left-type="remove" right-type="add">
|
<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. -->
|
<!-- 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]
|
[Yet another random diff thread content here]
|
||||||
</gr-diff-comment-thread></td>
|
</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. -->
|
<!-- 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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
<tbody class="section both">
|
<tbody class="section both">
|
||||||
<tr class="diff-row side-by-side" left-type="both" right-type="both">
|
<tr class="diff-row side-by-side" left-type="both" right-type="both">
|
||||||
<td class="left lineNum" data-value="141"></td>
|
<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="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>
|
<td class="content both"><div class="contentText">nam et complectitur verbis, quod vult, et dicit plane, quod intellegam;</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -95,7 +102,7 @@ limitations under the License.
|
|||||||
<td class="left lineNum" data-value="165"></td>
|
<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="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="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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
@@ -121,7 +128,7 @@ limitations under the License.
|
|||||||
|
|
||||||
setup(function() {
|
setup(function() {
|
||||||
sandbox = sinon.sandbox.create();
|
sandbox = sinon.sandbox.create();
|
||||||
element = fixture('basic');
|
element = fixture('basic')[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
teardown(function() {
|
teardown(function() {
|
||||||
@@ -290,7 +297,7 @@ limitations under the License.
|
|||||||
startLine: 119,
|
startLine: 119,
|
||||||
startChar: 10,
|
startChar: 10,
|
||||||
endLine: 120,
|
endLine: 120,
|
||||||
endChar: 34,
|
endChar: 36,
|
||||||
});
|
});
|
||||||
assert.equal(getActionSide(), 'right');
|
assert.equal(getActionSide(), 'right');
|
||||||
});
|
});
|
||||||
@@ -352,7 +359,7 @@ limitations under the License.
|
|||||||
startLine: 140,
|
startLine: 140,
|
||||||
startChar: 2,
|
startChar: 2,
|
||||||
endLine: 140,
|
endLine: 140,
|
||||||
endChar: 60,
|
endChar: 61,
|
||||||
});
|
});
|
||||||
assert.equal(getActionSide(), 'left');
|
assert.equal(getActionSide(), 'left');
|
||||||
});
|
});
|
||||||
@@ -361,7 +368,7 @@ limitations under the License.
|
|||||||
var contentText = stubContent(140, 'left');
|
var contentText = stubContent(140, 'left');
|
||||||
var contentTd = contentText.parentElement;
|
var contentTd = contentText.parentElement;
|
||||||
|
|
||||||
emulateSelection(contentTd.previousElementSibling.firstChild, 2,
|
emulateSelection(contentTd.previousElementSibling, 0,
|
||||||
contentText.firstChild, 2);
|
contentText.firstChild, 2);
|
||||||
assert.isFalse(element.isRangeSelected());
|
assert.isFalse(element.isRangeSelected());
|
||||||
});
|
});
|
||||||
@@ -389,7 +396,7 @@ limitations under the License.
|
|||||||
assert.isTrue(element.isRangeSelected());
|
assert.isTrue(element.isRangeSelected());
|
||||||
assert.deepEqual(getActionRange(), {
|
assert.deepEqual(getActionRange(), {
|
||||||
startLine: 140,
|
startLine: 140,
|
||||||
startChar: 81,
|
startChar: 83,
|
||||||
endLine: 141,
|
endLine: 141,
|
||||||
endChar: 4,
|
endChar: 4,
|
||||||
});
|
});
|
||||||
@@ -406,13 +413,14 @@ limitations under the License.
|
|||||||
startLine: 140,
|
startLine: 140,
|
||||||
startChar: 4,
|
startChar: 4,
|
||||||
endLine: 140,
|
endLine: 140,
|
||||||
endChar: 81,
|
endChar: 83,
|
||||||
});
|
});
|
||||||
assert.equal(getActionSide(), 'left');
|
assert.equal(getActionSide(), 'left');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('starts in context element', function() {
|
test('starts in context element', function() {
|
||||||
var contextControl = diff.querySelector('.contextControl');
|
var contextControl =
|
||||||
|
diff.querySelector('.contextControl').querySelector('gr-button');
|
||||||
var content = stubContent(146, 'right');
|
var content = stubContent(146, 'right');
|
||||||
emulateSelection(contextControl, 0, content.firstChild, 7);
|
emulateSelection(contextControl, 0, content.firstChild, 7);
|
||||||
// TODO (viktard): Select nearest line.
|
// TODO (viktard): Select nearest line.
|
||||||
@@ -420,9 +428,10 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('ends in context element', function() {
|
test('ends in context element', function() {
|
||||||
var contextControl = diff.querySelector('.contextControl');
|
var contextControl =
|
||||||
|
diff.querySelector('.contextControl').querySelector('gr-button');
|
||||||
var content = stubContent(141, 'left');
|
var content = stubContent(141, 'left');
|
||||||
emulateSelection(content.firstChild, 2, contextControl, 0);
|
emulateSelection(content.firstChild, 2, contextControl, 1);
|
||||||
// TODO (viktard): Select nearest line.
|
// TODO (viktard): Select nearest line.
|
||||||
assert.isFalse(element.isRangeSelected());
|
assert.isFalse(element.isRangeSelected());
|
||||||
});
|
});
|
||||||
@@ -459,13 +468,13 @@ limitations under the License.
|
|||||||
var content = stubContent(140, 'left');
|
var content = stubContent(140, 'left');
|
||||||
emulateSelection(
|
emulateSelection(
|
||||||
content.querySelectorAll('hl')[3], 0,
|
content.querySelectorAll('hl')[3], 0,
|
||||||
content.querySelectorAll('span')[1], 0);
|
content.querySelectorAll('span')[1].nextSibling, 1);
|
||||||
assert.isTrue(element.isRangeSelected());
|
assert.isTrue(element.isRangeSelected());
|
||||||
assert.deepEqual(getActionRange(), {
|
assert.deepEqual(getActionRange(), {
|
||||||
startLine: 140,
|
startLine: 140,
|
||||||
startChar: 51,
|
startChar: 51,
|
||||||
endLine: 140,
|
endLine: 140,
|
||||||
endChar: 68,
|
endChar: 71,
|
||||||
});
|
});
|
||||||
assert.equal(getActionSide(), 'left');
|
assert.equal(getActionSide(), 'left');
|
||||||
});
|
});
|
||||||
@@ -485,7 +494,7 @@ limitations under the License.
|
|||||||
var content = stubContent(140, 'left');
|
var content = stubContent(140, 'left');
|
||||||
var child = content.lastChild.lastChild;
|
var child = content.lastChild.lastChild;
|
||||||
var result = GrRangeNormalizer._getTextOffset(content, child);
|
var result = GrRangeNormalizer._getTextOffset(content, child);
|
||||||
assert.equal(result, 73);
|
assert.equal(result, 75);
|
||||||
content = stubContent(146, 'right');
|
content = stubContent(146, 'right');
|
||||||
child = content.lastChild;
|
child = content.lastChild;
|
||||||
result = GrRangeNormalizer._getTextOffset(content, child);
|
result = GrRangeNormalizer._getTextOffset(content, child);
|
||||||
@@ -496,6 +505,50 @@ limitations under the License.
|
|||||||
// TODO (viktard): Empty lines in selection end.
|
// TODO (viktard): Empty lines in selection end.
|
||||||
// TODO (viktard): Only empty lines selected.
|
// TODO (viktard): Only empty lines selected.
|
||||||
// TODO (viktard): Unified mode.
|
// 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>
|
</script>
|
||||||
|
Reference in New Issue
Block a user