Add multi-matches lines
Update ResultRowGroup and MatchRow so that lines that include multiple
matches for the search are displayed on one row, with each match being
highlighted.
See issue for details / illustration.
MODIFY
lib/project/result-row-view.js
lib/project/result-row-view.js
@@ -88,7 +88,7 @@ class ResultPathRowView {
class MatchRowView {
constructor({rowData, groupData, isSelected, replacePattern, regex}) {
const props = {rowData, groupData, isSelected, replacePattern, regex};
- const previewData = {match: rowData.match, replacePattern, regex};
+ const previewData = {matches: rowData.matches, replacePattern, regex};
this.props = Object.assign({}, props);
this.previewData = previewData;
@@ -99,7 +99,7 @@ class MatchRowView {
update({rowData, groupData, isSelected, replacePattern, regex}) {
const props = {rowData, groupData, isSelected, replacePattern, regex};
- const previewData = {match: rowData.match, replacePattern, regex};
+ const previewData = {matches: rowData.matches, replacePattern, regex};
if (!_.isEqual(props, this.props)) {
if (!_.isEqual(previewData, this.previewData)) {
@@ -111,13 +111,17 @@ class MatchRowView {
}
}
- generatePreviewNode({match, replacePattern, regex}) {
+ generatePreviewNode({matches, replacePattern, regex}) {
+ const lineText = matches[0].lineText
+ const subnodes = [];
+
+ let prevEnd = 0;
+ for (const match of matches) {
const range = Range.fromObject(match.range);
const matchStart = range.start.column - match.lineTextOffset;
const matchEnd = range.end.column - match.lineTextOffset;
- const prefix = match.lineText.slice(0, matchStart);
- const suffix = match.lineText.slice(matchEnd);
+ const prefix = match.lineText.slice(prevEnd, matchStart);
let replacementText = ''
if (replacePattern && regex) {
@@ -126,17 +130,31 @@ class MatchRowView {
replacementText = replacePattern;
}
- return $.span(
- {className: 'preview'},
+ subnodes.push(
$.span({}, prefix),
$.span(
- {className: `match ${replacementText ? 'highlight-error' : 'highlight-info'}`},
+ {
+ className:
+ `match ${replacementText ? 'highlight-error' : 'highlight-info'}`
+ },
match.matchText
),
$.span(
- {className: 'replacement highlight-success', style: showIf(replacementText)},
+ {
+ className: 'replacement highlight-success',
+ style: showIf(replacementText)
+ },
replacementText
- ),
+ )
+ );
+ prevEnd = matchEnd;
+ }
+
+ const suffix = lineText.slice(prevEnd);
+
+ return $.span(
+ {className: 'preview'},
+ ...subnodes,
$.span({}, suffix)
);
}

MODIFY
lib/project/result-row.js
lib/project/result-row.js
@@ -36,13 +36,13 @@ class ResultPathRow {
}
class MatchRow {
- constructor(rowGroup, separator, lineNumber, match) {
+ constructor(rowGroup, separator, lineNumber, matches) {
this.group = rowGroup
this.data = {
separator,
lineNumber,
matchLineNumber: lineNumber,
- match,
+ matches,
}
}
}
@@ -77,6 +77,7 @@ class ResultRowGroup {
// result; the added complexity comes from the fact that there musn't be
// context lines between adjacent match lines
let prevMatch = null
+ let prevMatchRow = null
let prevLineNumber
for (const match of this.result.matches) {
const { leadingContextLines } = match
@@ -84,26 +85,32 @@ class ResultRowGroup {
let leadCount
if (prevMatch) {
- let dy = Math.max(lineNumber - prevLineNumber - 1, 0)
+ const interval = Math.max(lineNumber - prevLineNumber - 1, 0)
- const trailCount = Math.min(trailingContextLineCount, dy)
+ const trailCount = Math.min(trailingContextLineCount, interval)
const { trailingContextLines } = prevMatch
rowArrays.push(
trailingContextLines.slice(0, trailCount).map((line, i) => (
new TrailingContextRow(this, line, false, prevLineNumber, i + 1)
))
)
- leadCount = Math.min(leadingContextLineCount, dy - trailCount)
+ leadCount = Math.min(leadingContextLineCount, interval - trailCount)
} else {
leadCount = Math.min(leadingContextLineCount, leadingContextLines.length)
}
rowArrays.push(
- leadingContextLines.slice(0, leadCount).map((line, i) => (
+ leadingContextLines.slice(leadingContextLines.length - leadCount).map((line, i) => (
new LeadingContextRow(this, line, false, lineNumber, leadCount - i)
))
)
- rowArrays.push([ new MatchRow(this, false, lineNumber, match) ])
+
+ if (prevMatchRow && lineNumber === prevLineNumber) {
+ prevMatchRow.data.matches.push(match)
+ } else {
+ prevMatchRow = new MatchRow(this, false, lineNumber, [match])
+ rowArrays.push([ prevMatchRow ])
+ }
prevMatch = match
prevLineNumber = lineNumber

MODIFY
lib/project/results-view.js
lib/project/results-view.js
@@ -473,9 +473,7 @@ class ResultsView {
const selectedRow = this.selectedRow()
if (selectedRow instanceof MatchRow) {
- // TODO - Multimatch rows
-
- const match = selectedRow.data.match
+ const match = selectedRow.data.matches[0]
const editor = await atom.workspace.open(selectedRow.group.result.filePath, {
pending,
split: reverseDirections[atom.config.get('find-and-replace.projectSearchResultsPaneSplitDirection')]
@@ -496,8 +494,10 @@ class ResultsView {
}
const selectedRow = this.selectedRow()
- if (selectedRow.data.match) {
- atom.clipboard.write(selectedRow.data.match.lineText);
+ if (selectedRow.data.matches) {
+ // TODO - If row has multiple matches, copy them all
+ // like when doing multi-cursor copying
+ atom.clipboard.write(selectedRow.data.matches[0].lineText);
}
}

MODIFY
spec/project-find-view-spec.js
spec/project-find-view-spec.js
@@ -320,7 +320,8 @@ describe('ProjectFindView', () => {
const resultsView = getResultsView();
await resultsView.heightInvalidationPromise
expect(resultsView.element).toBeVisible();
- expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(3);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(2);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match.highlight-info")).toHaveLength(3);
});
it("doesn't insert a escaped char if there are multiple backslashs in front of the char", async () => {
@@ -697,7 +698,8 @@ describe('ProjectFindView', () => {
const resultsView = getResultsView();
await resultsView.heightInvalidationPromise
expect(resultsView.element).toBeVisible();
- expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(13);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(11);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match.highlight-info")).toHaveLength(13);
})
});
@@ -757,7 +759,8 @@ describe('ProjectFindView', () => {
await resultsView.heightInvalidationPromise
expect(resultsView.element).toBeVisible();
- expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(13);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(11);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match.highlight-info")).toHaveLength(13);
expect(resultsPaneView.refs.previewCount.textContent).toBe("13 results found in 2 files for items");
expect(projectFindView.errorMessages).not.toBeVisible();
@@ -783,11 +786,12 @@ describe('ProjectFindView', () => {
const resultsPaneView = getExistingResultsPane();
await resultsView.heightInvalidationPromise
- expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(13);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(11);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match.highlight-info")).toHaveLength(13);
expect(resultsPaneView.refs.previewCount.textContent).toBe("13 results found in 2 files for items");
resultsView.selectFirstResult();
- for (let i = 0; i < 7; i++) resultsView.moveDown();
+ for (let i = 0; i < 6; i++) resultsView.moveDown();
await resultsView.moveDown();
expect(resultsView.refs.listView.element.querySelectorAll(".path-row")[1]).toHaveClass('selected');
@@ -1035,7 +1039,8 @@ describe('ProjectFindView', () => {
resultsView.scrollToBottom(); // To load ALL the results
await etch.update(resultsView);
expect(resultsView.element).toBeVisible();
- expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(13);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match-row")).toHaveLength(11);
+ expect(resultsView.refs.listView.element.querySelectorAll(".match.highlight-info")).toHaveLength(13);
resultsView.selectFirstResult();
for (let i = 0; i < 9; i++) resultsView.moveDown();

MODIFY
spec/result-row-spec.js
spec/result-row-spec.js
@@ -35,11 +35,11 @@ describe("ResultRowGroup", () => {
const expectedRows = [
new ResultPathRow(rowGroup),
- new MatchRow(rowGroup, false, 0, result.matches[0]),
- new MatchRow(rowGroup, true, 7, result.matches[1]),
- new MatchRow(rowGroup, true, 13, result.matches[2]),
- new MatchRow(rowGroup, true, 16, result.matches[3]),
- new MatchRow(rowGroup, false, 17, result.matches[4])
+ new MatchRow(rowGroup, false, 0, [ result.matches[0] ]),
+ new MatchRow(rowGroup, true, 7, [ result.matches[1] ]),
+ new MatchRow(rowGroup, true, 13, [ result.matches[2] ]),
+ new MatchRow(rowGroup, true, 16, [ result.matches[3] ]),
+ new MatchRow(rowGroup, false, 17, [ result.matches[4] ])
]
for (let i = 0; i < rowGroup.rows.length; ++i) {
@@ -56,26 +56,26 @@ describe("ResultRowGroup", () => {
const expectedRows = [
new ResultPathRow(rowGroup),
- new MatchRow(rowGroup, false, 0, result.matches[0]),
+ new MatchRow(rowGroup, false, 0, [ result.matches[0] ]),
new TrailingContextRow(rowGroup, lines[1], false, 0, 1),
new TrailingContextRow(rowGroup, lines[2], false, 0, 2),
new LeadingContextRow(rowGroup, lines[4], true, 7, 3),
new LeadingContextRow(rowGroup, lines[5], false, 7, 2),
new LeadingContextRow(rowGroup, lines[6], false, 7, 1),
- new MatchRow(rowGroup, false, 7, result.matches[1]),
+ new MatchRow(rowGroup, false, 7, [ result.matches[1] ]),
new TrailingContextRow(rowGroup, lines[8], false, 7, 1),
new TrailingContextRow(rowGroup, lines[9], false, 7, 2),
new LeadingContextRow(rowGroup, lines[10], false, 13, 3),
new LeadingContextRow(rowGroup, lines[11], false, 13, 2),
new LeadingContextRow(rowGroup, lines[12], false, 13, 1),
- new MatchRow(rowGroup, false, 13, result.matches[2]),
+ new MatchRow(rowGroup, false, 13, [ result.matches[2] ]),
new TrailingContextRow(rowGroup, lines[14], false, 13, 1),
new TrailingContextRow(rowGroup, lines[15], false, 13, 2),
- new MatchRow(rowGroup, false, 16, result.matches[3]),
- new MatchRow(rowGroup, false, 17, result.matches[4])
+ new MatchRow(rowGroup, false, 16, [ result.matches[3] ]),
+ new MatchRow(rowGroup, false, 17, [ result.matches[4] ])
]
for (let i = 0; i < rowGroup.rows.length; ++i) {

MODIFY
spec/results-view-spec.js
spec/results-view-spec.js
@@ -517,11 +517,11 @@ describe('ResultsView', () => {
await resultsView.heightInvalidationPromise;
let {length: resultCount} = resultsView.refs.listView.element.querySelectorAll(".match-row");
- expect(resultCount).toBe(13);
+ expect(resultCount).toBe(11);
resultsView.selectFirstResult();
- // moves down for 13 results + 2 files
+ // moves down for 11 results + 2 files
for (let i = 0; i < resultCount; ++i) {
resultsView.moveDown();
}
@@ -947,7 +947,7 @@ describe('ResultsView', () => {
it('maintains selected result when adding and removing results', async () => {
{
const matchRows = resultsView.refs.listView.element.querySelectorAll('.match-row');
- expect(matchRows.length).toBe(4);
+ expect(matchRows.length).toBe(3);
resultsView.moveDown();
resultsView.moveDown();
@@ -967,7 +967,7 @@ describe('ResultsView', () => {
// check that the same match is still selected
{
const matchRows = resultsView.refs.listView.element.querySelectorAll('.match-row');
- expect(matchRows.length).toBe(2);
+ expect(matchRows.length).toBe(1);
expect(matchRows[0]).toHaveClass('selected');
expect(matchRows[0].querySelector('.preview').textContent).toBe(' current < pivot ? left.push(current) : right.push(current);');
expect(resultsView.selectedRowIndex).toBe(1);
@@ -980,7 +980,7 @@ describe('ResultsView', () => {
// check that the same match is still selected
{
const matchRows = resultsView.refs.listView.element.querySelectorAll('.match-row');
- expect(matchRows.length).toBe(4);
+ expect(matchRows.length).toBe(3);
expect(matchRows[2]).toHaveClass('selected');
expect(matchRows[2].querySelector('.preview').textContent).toBe(' current < pivot ? left.push(current) : right.push(current);');
expect(resultsView.selectedRowIndex).toBe(4);

Add multi-matches lines

Update ResultRowGroup and MatchRow so that lines that include multiple
matches for the search are displayed on one row, with each match being
highlighted.

See issue #935 for details / illustration.