add separate + / - buttons for leading as well as trailing context lines
MODIFY
lib/find-options.coffee
lib/find-options.coffee
@@ -9,7 +9,8 @@ Params = [
'wholeWord'
'caseSensitive'
'inCurrentSelection'
- 'showContextLines'
+ 'leadingContextLineCount'
+ 'trailingContextLineCount'
]
module.exports =
@@ -24,7 +25,8 @@ class FindOptions
@caseSensitive = state.caseSensitive ? atom.config.get('find-and-replace.caseSensitive') ? false
@wholeWord = state.wholeWord ? atom.config.get('find-and-replace.wholeWord') ? false
@inCurrentSelection = state.inCurrentSelection ? atom.config.get('find-and-replace.inCurrentSelection') ? false
- @showContextLines = state.showContextLines ? atom.config.get('find-and-replace.showContextLines') ? false
+ @leadingContextLineCount = state.leadingContextLineCount ? atom.config.get('find-and-replace.leadingContextLineCount') ? 0
+ @trailingContextLineCount = state.trailingContextLineCount ? atom.config.get('find-and-replace.trailingContextLineCount') ? 0
onDidChange: (callback) ->
@emitter.on('did-change', callback)

MODIFY
lib/project/match-view.js
lib/project/match-view.js
@@ -5,18 +5,19 @@ const $ = etch.dom;
module.exports =
class MatchView {
- constructor({match, regex, replacePattern, isSelected, previewStyle, top, showContextLines}) {
+ constructor({match, regex, replacePattern, isSelected, previewStyle, top, leadingContextLineCount, trailingContextLineCount}) {
this.match = match;
this.regex = regex;
this.replacePattern = replacePattern;
this.previewStyle = previewStyle;
this.isSelected = isSelected;
this.top = top;
- this.showContextLines = showContextLines;
+ this.leadingContextLineCount = leadingContextLineCount;
+ this.trailingContextLineCount = trailingContextLineCount;
etch.initialize(this);
}
- update({match, regex, replacePattern, previewStyle, isSelected, top, showContextLines}) {
+ update({match, regex, replacePattern, previewStyle, isSelected, top, leadingContextLineCount, trailingContextLineCount}) {
const changed =
match !== this.match ||
regex !== this.regex ||
@@ -24,7 +25,8 @@ class MatchView {
previewStyle !== this.previewStyle ||
isSelected !== this.isSelected ||
top !== this.top ||
- showContextLines !== this.showContextLines;
+ leadingContextLineCount !== this.leadingContextLineCount ||
+ trailingContextLineCount !== this.trailingContextLineCount;
if (changed) {
this.match = match;
@@ -33,7 +35,8 @@ class MatchView {
this.previewStyle = previewStyle;
this.isSelected = isSelected;
this.top = top;
- this.showContextLines = showContextLines;
+ this.leadingContextLineCount = leadingContextLineCount;
+ this.trailingContextLineCount = trailingContextLineCount;
etch.update(this);
}
}
@@ -68,11 +71,12 @@ class MatchView {
},
$.ul({className: 'list-tree'},
- leadingContextLines && this.showContextLines ? leadingContextLines.map((line, index) =>
+ leadingContextLines && this.leadingContextLineCount ? leadingContextLines.map((line, index) =>
+ index >= leadingContextLines.length - this.leadingContextLineCount ?
$.li({className: 'list-item'},
$.span({className: 'line-number text-subtle'}, range.start.row + 1 - leadingContextLines.length + index),
$.span({className: 'preview'}, $.span({}, line.trimLeft()))
- )
+ ) : null
) : null,
$.li({className: `list-item match-line ${this.isSelected ? 'selected' : ''}`},
@@ -92,11 +96,12 @@ class MatchView {
)
),
- trailingContextLines && this.showContextLines ? trailingContextLines.map((line, index) =>
+ trailingContextLines && this.trailingContextLineCount ? trailingContextLines.map((line, index) =>
+ index < this.trailingContextLineCount ?
$.li({className: 'list-item'},
$.span({className: 'line-number text-subtle'}, range.end.row + 1 + index + 1),
$.span({className: 'preview'}, $.span({}, line.trimLeft()))
- )
+ ) : null
) : null
)
)

MODIFY
lib/project/result-view.js
lib/project/result-view.js
@@ -10,7 +10,7 @@ class ResultView {
const {
filePath, matches, isSelected, selectedMatchIndex, isExpanded, regex,
replacePattern, previewStyle, pathDetailsHeight, matchHeight, contextLineHeight,
- showContextLines
+ leadingContextLineCount, trailingContextLineCount
} = item;
this.top = top;
@@ -18,7 +18,8 @@ class ResultView {
this.pathDetailsHeight = pathDetailsHeight;
this.matchHeight = matchHeight;
this.contextLineHeight = contextLineHeight;
- this.showContextLines = showContextLines;
+ this.leadingContextLineCount = leadingContextLineCount;
+ this.trailingContextLineCount = trailingContextLineCount;
this.filePath = filePath
this.matches = matches
@@ -35,7 +36,7 @@ class ResultView {
const {
filePath, matches, isSelected, selectedMatchIndex, isExpanded, regex,
replacePattern, previewStyle, pathDetailsHeight, matchHeight, contextLineHeight,
- showContextLines
+ leadingContextLineCount, trailingContextLineCount
} = item;
const changed =
@@ -50,7 +51,8 @@ class ResultView {
bottom !== this.bottom ||
pathDetailsHeight !== this.pathDetailsHeight ||
contextLineHeight !== this.contextLineHeight ||
- showContextLines !== this.showContextLines ||
+ leadingContextLineCount !== this.leadingContextLineCount ||
+ trailingContextLineCount !== this.trailingContextLineCount ||
matchHeight !== this.matchHeight;
if (changed) {
@@ -67,7 +69,8 @@ class ResultView {
this.pathDetailsHeight = pathDetailsHeight;
this.matchHeight = matchHeight;
this.contextLineHeight = contextLineHeight;
- this.showContextLines = showContextLines;
+ this.leadingContextLineCount = leadingContextLineCount;
+ this.trailingContextLineCount = trailingContextLineCount;
etch.update(this);
}
}
@@ -134,8 +137,8 @@ class ResultView {
for (; i < this.matches.length; i++) {
const match = this.matches[i];
let itemBottomPosition = itemTopPosition + this.matchHeight;
- if (match.leadingContextLines && this.showContextLines) itemBottomPosition += match.leadingContextLines.length * this.contextLineHeight;
- if (match.trailingContextLines && this.showContextLines) itemBottomPosition += match.trailingContextLines.length * this.contextLineHeight;
+ if (match.leadingContextLines && this.leadingContextLineCount) itemBottomPosition += this.leadingContextLineCount * this.contextLineHeight;
+ if (match.trailingContextLines && this.trailingContextLineCount) itemBottomPosition += this.trailingContextLineCount * this.contextLineHeight;
if (itemBottomPosition > top) break;
itemTopPosition = itemBottomPosition;
@@ -144,8 +147,8 @@ class ResultView {
for (; i < this.matches.length; i++) {
const match = this.matches[i];
let itemBottomPosition = itemTopPosition + this.matchHeight;
- if (match.leadingContextLines && this.showContextLines) itemBottomPosition += match.leadingContextLines.length * this.contextLineHeight;
- if (match.trailingContextLines && this.showContextLines) itemBottomPosition += match.trailingContextLines.length * this.contextLineHeight;
+ if (match.leadingContextLines && this.leadingContextLineCount) itemBottomPosition += this.leadingContextLineCount * this.contextLineHeight;
+ if (match.trailingContextLines && this.trailingContextLineCount) itemBottomPosition += this.trailingContextLineCount * this.contextLineHeight;
children.push(
etch.dom(MatchView, {
@@ -156,7 +159,8 @@ class ResultView {
isSelected: (i === this.selectedMatchIndex),
previewStyle: this.previewStyle,
top: itemTopPosition,
- showContextLines: this.showContextLines
+ leadingContextLineCount: this.leadingContextLineCount,
+ trailingContextLineCount: this.trailingContextLineCount
})
);

MODIFY
lib/project/results-pane.js
lib/project/results-pane.js
@@ -15,6 +15,8 @@ class ResultsPaneView {
this.searchResults = null;
this.searchingIsSlow = false;
this.numberOfPathsSearched = 0;
+ this.searchContextLineCountBefore = 0;
+ this.searchContextLineCountAfter = 0;
etch.initialize(this);
@@ -28,8 +30,23 @@ class ResultsPaneView {
case this.refs.expandAll:
this.expandAllResults();
break;
- case this.refs.toggleContext:
- this.toggleContextLines();
+ case this.refs.decrementLeadingContextLines:
+ this.decrementLeadingContextLines();
+ break;
+ case this.refs.toggleLeadingContextLines:
+ this.toggleLeadingContextLines();
+ break;
+ case this.refs.incrementLeadingContextLines:
+ this.incrementLeadingContextLines();
+ break;
+ case this.refs.decrementTrailingContextLines:
+ this.decrementTrailingContextLines();
+ break;
+ case this.refs.toggleTrailingContextLines:
+ this.toggleTrailingContextLines();
+ break;
+ case this.refs.incrementTrailingContextLines:
+ this.incrementTrailingContextLines();
break;
}
})
@@ -40,7 +57,9 @@ class ResultsPaneView {
this.model.onDidClear(this.onCleared.bind(this)),
this.model.onDidClearReplacementState(this.onReplacementStateCleared.bind(this)),
this.model.onDidSearchPaths(this.onPathsSearched.bind(this)),
- this.model.onDidErrorForPath(error => this.appendError(error.message))
+ this.model.onDidErrorForPath(error => this.appendError(error.message)),
+ atom.config.observe('find-and-replace.searchContextLineCountBefore', this.searchContextLineCountChanged.bind(this)),
+ atom.config.observe('find-and-replace.searchContextLineCountAfter', this.searchContextLineCountChanged.bind(this))
);
}
@@ -54,10 +73,6 @@ class ResultsPaneView {
render() {
const matchCount = this.searchResults && this.searchResults.matchCount;
- const leadingContextLineCount = atom.config.get('find-and-replace.searchContextLineCountBefore');
- const trailingContextLineCount = atom.config.get('find-and-replace.searchContextLineCountAfter');
- const hasContext = leadingContextLineCount > 0 || trailingContextLineCount > 0;
-
return (
$.div(
{
@@ -82,13 +97,42 @@ class ResultsPaneView {
},
$.div({className: 'btn-toolbar'},
- hasContext ?
+ this.searchContextLineCountBefore > 0 ?
$.div({className: 'btn-group'},
$.button(
{
- ref: 'toggleContext',
- className: 'btn' + (this.model.getFindOptions().showContextLines ? ' selected' : '')
- }, 'Show Context')
+ ref: 'decrementLeadingContextLines',
+ className: 'btn' + (this.model.getFindOptions().leadingContextLineCount === 0 ? ' disabled' : '')
+ }, '-'),
+ $.button(
+ {
+ ref: 'toggleLeadingContextLines',
+ className: 'btn'
+ }, this.model.getFindOptions().leadingContextLineCount + ' before'),
+ $.button(
+ {
+ ref: 'incrementLeadingContextLines',
+ className: 'btn' + (this.model.getFindOptions().leadingContextLineCount >= this.searchContextLineCountBefore ? ' disabled' : '')
+ }, '+')
+ ) : null,
+
+ this.searchContextLineCountAfter > 0 ?
+ $.div({className: 'btn-group'},
+ $.button(
+ {
+ ref: 'decrementTrailingContextLines',
+ className: 'btn' + (this.model.getFindOptions().trailingContextLineCount === 0 ? ' disabled' : '')
+ }, '-'),
+ $.button(
+ {
+ ref: 'toggleTrailingContextLines',
+ className: 'btn'
+ }, this.model.getFindOptions().trailingContextLineCount + ' after'),
+ $.button(
+ {
+ ref: 'incrementTrailingContextLines',
+ className: 'btn' + (this.model.getFindOptions().trailingContextLineCount >= this.searchContextLineCountAfter ? ' disabled' : '')
+ }, '+')
) : null,
$.div({className: 'btn-group'},
@@ -226,14 +270,53 @@ class ResultsPaneView {
this.refs.resultsView.element.focus();
}
- toggleContextLines() {
- this.refs.resultsView.toggleContextLines();
- if (this.model.getFindOptions().showContextLines) {
- this.refs.toggleContext.classList.add('selected');
- } else {
- this.refs.toggleContext.classList.remove('selected');
+ decrementLeadingContextLines() {
+ this.refs.resultsView.decrementLeadingContextLines();
+ etch.update(this);
+ }
+
+ toggleLeadingContextLines() {
+ this.refs.resultsView.toggleLeadingContextLines();
+ etch.update(this);
+ }
+
+ incrementLeadingContextLines() {
+ this.refs.resultsView.incrementLeadingContextLines();
+ etch.update(this);
+ }
+
+ decrementTrailingContextLines() {
+ this.refs.resultsView.decrementTrailingContextLines();
+ etch.update(this);
+ }
+
+ toggleTrailingContextLines() {
+ this.refs.resultsView.toggleTrailingContextLines();
+ etch.update(this);
+ }
+
+ incrementTrailingContextLines() {
+ this.refs.resultsView.incrementTrailingContextLines();
+ etch.update(this);
+ }
+
+ searchContextLineCountChanged() {
+ this.searchContextLineCountBefore = atom.config.get('find-and-replace.searchContextLineCountBefore');
+ this.searchContextLineCountAfter = atom.config.get('find-and-replace.searchContextLineCountAfter');
+ // update the visible line count in the find options to not exceed the maximum available lines
+ let findOptionsChanged = false;
+ if (this.searchContextLineCountBefore < this.model.getFindOptions().leadingContextLineCount) {
+ this.model.getFindOptions().leadingContextLineCount = this.searchContextLineCountBefore;
+ findOptionsChanged = true;
+ }
+ if (this.searchContextLineCountAfter < this.model.getFindOptions().trailingContextLineCount) {
+ this.model.getFindOptions().trailingContextLineCount = this.searchContextLineCountAfter;
+ findOptionsChanged = true;
+ }
+ etch.update(this);
+ if (findOptionsChanged) {
+ etch.update(this.refs.resultsView);
}
- this.refs.resultsView.element.focus();
}
}

MODIFY
lib/project/results-view.js
lib/project/results-view.js
@@ -126,7 +126,8 @@ class ResultsView {
pathDetailsHeight: this.pathDetailsHeight,
matchHeight: this.matchHeight,
contextLineHeight: this.contextLineHeight,
- showContextLines: this.model.getFindOptions().showContextLines
+ leadingContextLineCount: this.model.getFindOptions().leadingContextLineCount,
+ trailingContextLineCount: this.model.getFindOptions().trailingContextLineCount
}, this.model.results[filePath]);
}),
})
@@ -139,11 +140,11 @@ class ResultsView {
for (let i = 0, n = searchResult.matches.length; i < n; i++) {
const match = searchResult.matches[i];
result += this.matchHeight;
- if (match.leadingContextLines && this.model.getFindOptions().showContextLines) {
- result += this.contextLineHeight * match.leadingContextLines.length;
+ if (match.leadingContextLines && this.model.getFindOptions().leadingContextLineCount) {
+ result += this.contextLineHeight * this.model.getFindOptions().leadingContextLineCount;
}
- if (match.trailingContextLines && this.model.getFindOptions().showContextLines) {
- result += this.contextLineHeight * match.trailingContextLines.length;
+ if (match.trailingContextLines && this.model.getFindOptions().trailingContextLineCount) {
+ result += this.contextLineHeight * this.model.getFindOptions().trailingContextLineCount;
}
}
}
@@ -445,8 +446,63 @@ class ResultsView {
etch.update(this);
}
- toggleContextLines() {
- this.model.getFindOptions().showContextLines = !this.model.getFindOptions().showContextLines;
+ decrementLeadingContextLines() {
+ if (this.model.getFindOptions().leadingContextLineCount > 0) {
+ this.model.getFindOptions().leadingContextLineCount--;
+ this.contextLinesChanged();
+ }
+ }
+
+ toggleLeadingContextLines() {
+ if (this.model.getFindOptions().leadingContextLineCount > 0) {
+ this.model.getFindOptions().leadingContextLineCount = 0;
+ this.contextLinesChanged();
+ } else {
+ const searchContextLineCountBefore = atom.config.get('find-and-replace.searchContextLineCountBefore');
+ if (this.model.getFindOptions().leadingContextLineCount < searchContextLineCountBefore) {
+ this.model.getFindOptions().leadingContextLineCount = searchContextLineCountBefore;
+ this.contextLinesChanged();
+ }
+ }
+ }
+
+ incrementLeadingContextLines() {
+ const searchContextLineCountBefore = atom.config.get('find-and-replace.searchContextLineCountBefore');
+ if (this.model.getFindOptions().leadingContextLineCount < searchContextLineCountBefore) {
+ this.model.getFindOptions().leadingContextLineCount++;
+ this.contextLinesChanged();
+ }
+ }
+
+ decrementTrailingContextLines() {
+ if (this.model.getFindOptions().trailingContextLineCount > 0) {
+ this.model.getFindOptions().trailingContextLineCount--;
+ this.contextLinesChanged();
+ }
+ }
+
+ toggleTrailingContextLines() {
+ if (this.model.getFindOptions().trailingContextLineCount > 0) {
+ this.model.getFindOptions().trailingContextLineCount = 0;
+ this.contextLinesChanged();
+ } else {
+ const searchContextLineCountAfter = atom.config.get('find-and-replace.searchContextLineCountAfter');
+ if (this.model.getFindOptions().trailingContextLineCount < searchContextLineCountAfter) {
+ this.model.getFindOptions().trailingContextLineCount = searchContextLineCountAfter;
+ this.contextLinesChanged();
+ }
+ }
+ }
+
+ incrementTrailingContextLines() {
+ const searchContextLineCountAfter = atom.config.get('find-and-replace.searchContextLineCountAfter');
+ if (this.model.getFindOptions().trailingContextLineCount < searchContextLineCountAfter) {
+ this.model.getFindOptions().trailingContextLineCount++;
+ this.contextLinesChanged();
+ }
+ }
+
+ contextLinesChanged() {
this.setScrollTop(0);
return etch.update(this);
}

MODIFY
spec/results-view-spec.js
spec/results-view-spec.js
@@ -698,7 +698,8 @@ describe('ResultsView', () => {
beforeEach(async () => {
atom.config.set('find-and-replace.searchContextLineCountBefore', 2);
atom.config.set('find-and-replace.searchContextLineCountAfter', 1);
- atom.config.set('find-and-replace.showContextLines', false);
+ atom.config.set('find-and-replace.leadingContextLineCount', 0);
+ atom.config.set('find-and-replace.trailingContextLineCount', 0);
projectFindView.findEditor.setText('items');
atom.commands.dispatch(projectFindView.element, 'core:confirm');
@@ -711,9 +712,32 @@ describe('ResultsView', () => {
// the following condition is pretty hacky
// it doesn't work correctly for e.g. version 1.2
if (parseFloat(atom.getVersion()) >= 1.17) {
- expect(resultsView.model.getFindOptions().showContextLines).toBe(false);
- await resultsView.toggleContextLines();
- expect(resultsView.model.getFindOptions().showContextLines).toBe(true);
+ expect(resultsView.model.getFindOptions().leadingContextLineCount).toBe(0);
+ expect(resultsView.model.getFindOptions().trailingContextLineCount).toBe(0);
+ await resultsView.toggleLeadingContextLines();
+ expect(resultsView.model.getFindOptions().leadingContextLineCount).toBe(2);
+ expect(resultsView.model.getFindOptions().trailingContextLineCount).toBe(0);
+
+ {
+ const pathNodes = resultsView.refs.listView.element.querySelectorAll('.path');
+ expect(pathNodes.length).toBe(2);
+ const pathNameNode = pathNodes[0].querySelector('.path-name');
+ expect(pathNameNode.textContent).toBe('sample.coffee');
+ // the second file is sample.js which we don't use
+ const resultNode = pathNodes[0].querySelector('.search-result');
+ const lineNodes = resultNode.querySelectorAll('.list-item');
+ expect(lineNodes.length).toBe(2);
+ expect(lineNodes[0]).not.toHaveClass('match-line');
+ expect(lineNodes[0].querySelector('.preview').textContent).toBe('class quicksort');
+ expect(lineNodes[1]).toHaveClass('match-line');
+ expect(lineNodes[1].querySelector('.preview').textContent).toBe('sort: (items) ->');
+ }
+
+ await resultsView.toggleTrailingContextLines();
+ expect(resultsView.model.getFindOptions().leadingContextLineCount).toBe(2);
+ expect(resultsView.model.getFindOptions().trailingContextLineCount).toBe(1);
+
+ {
const pathNodes = resultsView.refs.listView.element.querySelectorAll('.path');
expect(pathNodes.length).toBe(2);
const pathNameNode = pathNodes[0].querySelector('.path-name');
@@ -729,13 +753,15 @@ describe('ResultsView', () => {
expect(lineNodes[2]).not.toHaveClass('match-line');
expect(lineNodes[2].querySelector('.preview').textContent).toBe('return items if items.length <= 1');
}
+ }
});
it('hides the context lines', async () => {
// the following condition is pretty hacky
// it doesn't work correctly for e.g. version 1.2
if (parseFloat(atom.getVersion()) >= 1.17) {
- expect(resultsView.model.getFindOptions().showContextLines).toBe(false);
+ expect(resultsView.model.getFindOptions().leadingContextLineCount).toBe(0);
+ expect(resultsView.model.getFindOptions().trailingContextLineCount).toBe(0);
const pathNodes = resultsView.refs.listView.element.querySelectorAll('.path');
expect(pathNodes.length).toBe(2);
let pathNameNode = pathNodes[0].querySelector('.path-name');

add separate + / - buttons for leading as well as trailing context lines