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

Handle multiline results on the find and replace UI #1085

Merged
merged 2 commits into from
May 24, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions lib/project-find-view.js
Original file line number Diff line number Diff line change
@@ -318,7 +318,7 @@ class ProjectFindView {
return searchPromise;
}

search(options) {
async search(options) {
// We always want to set the options passed in, even if we dont end up doing the search
if (options == null) { options = {}; }
this.model.getFindOptions().set(options);
@@ -330,13 +330,13 @@ class ProjectFindView {
let {onlyRunIfActive, onlyRunIfChanged} = options;
if ((onlyRunIfActive && !this.model.active) || !findPattern) return Promise.resolve();

return this.showResultPane().then(() => {
try {
return this.model.search(findPattern, pathsPattern, replacePattern, options);
} catch (e) {
this.setErrorMessage(e.message);
}
});
await this.showResultPane()

try {
return await this.model.search(findPattern, pathsPattern, replacePattern, options);
} catch (e) {
this.setErrorMessage(e.message);
}
}

replaceAll() {
84 changes: 63 additions & 21 deletions lib/project/results-model.js
Original file line number Diff line number Diff line change
@@ -5,16 +5,57 @@ const escapeHelper = require('../escape-helper')
class Result {
static create (result) {
if (result && result.matches && result.matches.length) {
const matches = result.matches.map(m =>
({
matchText: m.matchText,
lineText: m.lineText,
lineTextOffset: m.lineTextOffset,
range: Range.fromObject(m.range),
leadingContextLines: m.leadingContextLines,
trailingContextLines: m.trailingContextLines
})
)
const matches = []

for (const m of result.matches) {
const range = Range.fromObject(m.range)
const matchSplit = m.matchText.split('\n')
const linesSplit = m.lineText.split('\n')

// If the result spans across multiple lines, process each of
// them separately by creating separate `matches` objects for
// each line on the match.
for (let row = range.start.row; row <= range.end.row; row++) {
const lineText = linesSplit[row - range.start.row]
const matchText = matchSplit[row - range.start.row]

// When receiving multiline results from opened buffers, only
// the first result line is provided on the `lineText` property.
// This makes it impossible to properly render the part of the result
// that's part of other lines.
// In order to prevent an error we just need to ignore these parts.
if (lineText === undefined || matchText === undefined) {
continue
}

// Adapt the range column number based on which line we're at:
// - the first line of a multiline result will always start at the range start
// and will end at the end of the line.
// - middle lines will start at 0 and end at the end of the line
// - last line will start at 0 and end at the range end.
const startColumn = row === range.start.row ? range.start.column : 0
const endColumn = row === range.end.row ? range.end.column : lineText.length

matches.push({
matchText,
lineText,
lineTextOffset: m.lineTextOffset,
range: {
start: {
row,
column: startColumn
},
end: {
row,
column: endColumn
}
},
leadingContextLines: m.leadingContextLines,
trailingContextLines: m.trailingContextLines
})
}
}

return new Result({filePath: result.filePath, matches})
} else {
return null
@@ -146,7 +187,7 @@ module.exports = class ResultsModel {
)
}

search (findPattern, pathsPattern, replacePattern, options = {}) {
async search (findPattern, pathsPattern, replacePattern, options = {}) {
if (!this.shouldRerunSearch(findPattern, pathsPattern, options)) {
this.emitter.emit('did-noop-search')
return Promise.resolve()
@@ -195,17 +236,18 @@ module.exports = class ResultsModel {
})

this.emitter.emit('did-start-searching', this.inProgressSearchPromise)
return this.inProgressSearchPromise.then(message => {
if (message === 'cancelled') {
this.emitter.emit('did-cancel-searching')
} else {
const resultsSummary = this.getResultsSummary()

this.metricsReporter.sendSearchEvent(Date.now() - startTime, resultsSummary.matchCount)
this.inProgressSearchPromise = null
this.emitter.emit('did-finish-searching', resultsSummary)
}
})
const message = await this.inProgressSearchPromise

if (message === 'cancelled') {
this.emitter.emit('did-cancel-searching')
} else {
const resultsSummary = this.getResultsSummary()

this.metricsReporter.sendSearchEvent(Date.now() - startTime, resultsSummary.matchCount)
this.inProgressSearchPromise = null
this.emitter.emit('did-finish-searching', resultsSummary)
}
}

replace (pathsPattern, replacePattern, replacementPaths) {