Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Commit

Permalink
Add multi-matches lines
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
PoignardAzur committed Apr 14, 2018
1 parent 136048c commit 0c83a1a
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 55 deletions.
62 changes: 40 additions & 22 deletions lib/project/result-row-view.js
Expand Up @@ -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;
Expand All @@ -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)) {
Expand All @@ -111,32 +111,50 @@ class MatchRowView {
}
}

generatePreviewNode({match, replacePattern, regex}) {
const range = Range.fromObject(match.range);
const matchStart = range.start.column - match.lineTextOffset;
const matchEnd = range.end.column - match.lineTextOffset;
generatePreviewNode({matches, replacePattern, regex}) {
const lineText = matches[0].lineText
const subnodes = [];

const prefix = match.lineText.slice(0, matchStart);
const suffix = match.lineText.slice(matchEnd);
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;

let replacementText = ''
if (replacePattern && regex) {
replacementText = match.matchText.replace(regex, replacePattern);
} else if (replacePattern) {
replacementText = replacePattern;
const prefix = match.lineText.slice(prevEnd, matchStart);

let replacementText = ''
if (replacePattern && regex) {
replacementText = match.matchText.replace(regex, replacePattern);
} else if (replacePattern) {
replacementText = replacePattern;
}

subnodes.push(
$.span({}, prefix),
$.span(
{
className:
`match ${replacementText ? 'highlight-error' : 'highlight-info'}`
},
match.matchText
),
$.span(
{
className: 'replacement highlight-success',
style: showIf(replacementText)
},
replacementText
)
);
prevEnd = matchEnd;
}

const suffix = lineText.slice(prevEnd);

return $.span(
{className: 'preview'},
$.span({}, prefix),
$.span(
{className: `match ${replacementText ? 'highlight-error' : 'highlight-info'}`},
match.matchText
),
$.span(
{className: 'replacement highlight-success', style: showIf(replacementText)},
replacementText
),
...subnodes,
$.span({}, suffix)
);
}
Expand Down
21 changes: 14 additions & 7 deletions lib/project/result-row.js
Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -77,33 +77,40 @@ 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
const lineNumber = Range.fromObject(match.range).start.row

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
Expand Down
10 changes: 5 additions & 5 deletions lib/project/results-view.js
Expand Up @@ -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')]
Expand All @@ -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);
}
}

Expand Down
17 changes: 11 additions & 6 deletions spec/project-find-view-spec.js
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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);
})
});

Expand Down Expand Up @@ -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();
Expand All @@ -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');
Expand Down Expand Up @@ -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();
Expand Down
20 changes: 10 additions & 10 deletions spec/result-row-spec.js
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
10 changes: 5 additions & 5 deletions spec/results-view-spec.js
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 0c83a1a

Please sign in to comment.