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

Add 'Find in Open Files' feature #967

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions lib/find-options.coffee
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ _ = require 'underscore-plus'
Params = [
'findPattern'
'replacePattern'
'paths'
'pathsPattern'
'useRegex'
'wholeWord'
@@ -20,6 +21,7 @@ class FindOptions

@findPattern = ''
@replacePattern = state.replacePattern ? ''
@paths = state.paths ? []
@pathsPattern = state.pathsPattern ? ''
@useRegex = state.useRegex ? atom.config.get('find-and-replace.useRegex') ? false
@caseSensitive = state.caseSensitive ? atom.config.get('find-and-replace.caseSensitive') ? false
43 changes: 31 additions & 12 deletions lib/find.coffee
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ FindOptions = require './find-options'
BufferSearch = require './buffer-search'
getIconServices = require './get-icon-services'
FindView = require './find-view'
OpenFilesFindView = require './open-files-find-view'
ProjectFindView = require './project-find-view'
ResultsModel = require './project/results-model'
ResultsPaneView = require './project/results-pane'
@@ -49,26 +50,35 @@ module.exports =
else
@findModel.setEditor(null)

@subscriptions.add atom.commands.add '.find-and-replace, .project-find', 'window:focus-next-pane', ->
@subscriptions.add atom.commands.add '.find-and-replace, .open-files-find, .project-find', 'window:focus-next-pane', ->
atom.views.getView(atom.workspace).focus()

@subscriptions.add atom.commands.add 'atom-workspace', 'open-files-find:show', =>
@createViews()
showPanel @openFilesFindPanel, => @openFilesFindView.focusFindElement()

@subscriptions.add atom.commands.add 'atom-workspace', 'open-files-find:toggle', =>
@createViews()
togglePanel @openFilesFindPanel, => @openFilesFindView.focusFindElement()

@subscriptions.add atom.commands.add 'atom-workspace', 'project-find:show', =>
@createViews()
showPanel @projectFindPanel, @findPanel, => @projectFindView.focusFindElement()
showPanel @projectFindPanel, => @projectFindView.focusFindElement()

@subscriptions.add atom.commands.add 'atom-workspace', 'project-find:toggle', =>
@createViews()
togglePanel @projectFindPanel, @findPanel, => @projectFindView.focusFindElement()
togglePanel @projectFindPanel, => @projectFindView.focusFindElement()

@subscriptions.add atom.commands.add 'atom-workspace', 'project-find:show-in-current-directory', ({target}) =>
@createViews()
@findPanel.hide()
@openFilesFindPanel.hide()
@projectFindPanel.show()
@projectFindView.focusFindElement()
@projectFindView.findInCurrentlySelectedDirectory(target)

@subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:use-selection-as-find-pattern', =>
return if @projectFindPanel?.isVisible() or @findPanel?.isVisible()
return if @openFilesFindPanel?.isVisible() or @projectFindPanel?.isVisible() or @findPanel?.isVisible()
@createViews()

@subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:use-selection-as-replace-pattern', =>
@@ -77,15 +87,15 @@ module.exports =

@subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:toggle', =>
@createViews()
togglePanel @findPanel, @projectFindPanel, => @findView.focusFindEditor()
togglePanel @findPanel, => @findView.focusFindEditor()

@subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:show', =>
@createViews()
showPanel @findPanel, @projectFindPanel, => @findView.focusFindEditor()
showPanel @findPanel, => @findView.focusFindEditor()

@subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:show-replace', =>
@createViews()
showPanel @findPanel, @projectFindPanel, => @findView.focusReplaceEditor()
showPanel @findPanel, => @findView.focusReplaceEditor()

@subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:clear-history', =>
@findHistory.clear()
@@ -96,6 +106,7 @@ module.exports =
isMiniEditor = target.tagName is 'ATOM-TEXT-EDITOR' and target.hasAttribute('mini')
unless isMiniEditor
@findPanel?.hide()
@openFilesFindPanel?.hide()
@projectFindPanel?.hide()

@subscriptions.add atom.commands.add 'atom-workspace',
@@ -111,13 +122,13 @@ module.exports =
@selectNextObjects.set(editor, selectNext)
selectNext

showPanel = (panelToShow, panelToHide, postShowAction) ->
panelToHide.hide()
showPanel = (panelToShow, postShowAction) =>
@panels.map (p) => p.hide() unless p is panelToShow
panelToShow.show()
postShowAction?()

togglePanel = (panelToToggle, panelToHide, postToggleAction) ->
panelToHide.hide()
togglePanel = (panelToToggle, postToggleAction) =>
@panels.map (p) => p.hide() unless p is panelToToggle

if panelToToggle.isVisible()
panelToToggle.hide()
@@ -187,13 +198,16 @@ module.exports =
options = {findBuffer, replaceBuffer, pathsBuffer, findHistoryCycler, replaceHistoryCycler, pathsHistoryCycler}

@findView = new FindView(@findModel, options)

@openFilesFindView = new OpenFilesFindView(@resultsModel, options)
@projectFindView = new ProjectFindView(@resultsModel, options)

@findPanel = atom.workspace.addBottomPanel(item: @findView, visible: false, className: 'tool-panel panel-bottom')
@openFilesFindPanel = atom.workspace.addBottomPanel(item: @openFilesFindView, visible: false, className: 'tool-panel panel-bottom')
@projectFindPanel = atom.workspace.addBottomPanel(item: @projectFindView, visible: false, className: 'tool-panel panel-bottom')
@panels = [@findPanel, @openFilesFindPanel, @projectFindPanel]

@findView.setPanel(@findPanel)
@openFilesFindView.setPanel(@openFilesFindPanel)
@projectFindView.setPanel(@projectFindPanel)

# HACK: Soooo, we need to get the model to the pane view whenever it is
@@ -219,6 +233,11 @@ module.exports =
@findModel?.destroy()
@findModel = null

@openFilesFindPanel?.destroy()
@openFilesFindPanel = null
@openFilesFindView?.destroy()
@openFilesFindView = null

@projectFindPanel?.destroy()
@projectFindPanel = null
@projectFindView?.destroy()
508 changes: 508 additions & 0 deletions lib/open-files-find-view.js

Large diffs are not rendered by default.

108 changes: 108 additions & 0 deletions lib/project/results-model.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const _ = require('underscore-plus')
const {Emitter, TextEditor, Range} = require('atom')
const {PathReplacer, PathSearcher} = require('scandal')
const replacer = new PathReplacer()
const searcher = new PathSearcher()
const escapeHelper = require('../escape-helper')

class Result {
@@ -258,6 +261,78 @@ module.exports = class ResultsModel {
}
}

shouldRerunSearchPaths (findPattern, paths, replacePattern, options) {
if (options == null) { options = {} }
const {onlyRunIfChanged} = options
return !(onlyRunIfChanged && (findPattern != null) &&
(findPattern === this.lastFindPattern) && (paths === this.lastPaths))
}

searchPaths (findPattern, paths, replacePattern, options) {
if (options == null) { options = {} }
if (!this.shouldRerunSearchPaths(findPattern, paths, replacePattern, options)) {
this.emitter.emit('did-noop-search')
return Promise.resolve()
}

const {keepReplacementState} = options
if (keepReplacementState) {
this.clearSearchState()
} else {
this.clear()
}

this.lastFindPattern = findPattern
this.lastPaths = paths
this.findOptions.set(_.extend({findPattern, replacePattern, paths}, options))
this.regex = this.findOptions.getFindPatternRegex()

this.active = true

const leadingContextLineCount = atom.config.get('find-and-replace.searchContextLineCountBefore')
const trailingContextLineCount = atom.config.get('find-and-replace.searchContextLineCountAfter')

searcher.on('results-found', result => {
this.setResult(result.filePath, Result.create(result))
})

/* build a cancellable promise like workspace.scan does internally so that it
looks identical to #search to outsiders looking in */
const isCancelled = false
this.inProgressSearchPromise = new Promise((resolve, reject) => {
if (isCancelled) { resolve('cancelled') }
searcher.searchPaths(this.regex, paths, (results, errors) => {
if (errors) {
errors.forEach(e => {
this.searchErrors = this.searchErrors || []
this.searchErrors.push(e)
this.emitter.emit('did-error-for-path', e)
})
reject()
} else {
this.emitter.emit('did-search-paths', paths.length)
resolve(null)
}
})
})

this.inProgressSearchPromise.cancel = () => {
isCancelled = true
}
// this.inProgressSearchPromise.done = () => {}

this.emitter.emit('did-start-searching', this.inProgressSearchPromise)

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

replace (pathsPattern, replacePattern, replacementPaths) {
if (!this.findOptions.findPattern || (this.regex == null)) { return }

@@ -291,6 +366,39 @@ module.exports = class ResultsModel {
}).catch(e => console.error(e.stack))
}

replacePaths (replacePattern, replacementPaths) {
if (!this.findOptions.findPattern || (this.regex == null)) { return }

this.findOptions.set({replacePattern})

if (this.findOptions.useRegex) { replacePattern = escapeHelper.unescapeEscapeSequence(replacePattern) }

this.active = false // not active until the search is finished
this.replacedPathCount = 0
this.replacementCount = 0

const promise = pathReplacer.replacePaths(this.regex, replacePattern, replacementPaths, (result, error) => {
if (result) {
if (result.replacements) {
this.replacedPathCount++
this.replacementCount += result.replacements
}
this.emitter.emit('did-replace-path', result)
} else {
if (this.replacementErrors == null) { this.replacementErrors = [] }
this.replacementErrors.push(error)
this.emitter.emit('did-error-for-path', error)
}
})

this.emitter.emit('did-start-replacing', promise)
return promise.then(() => {
this.emitter.emit('did-finish-replacing', this.getResultsSummary())
return this.searchPaths(this.findOptions.findPattern, this.findOptions.paths,
this.findOptions.replacePattern, {keepReplacementState: true})
}).catch(e => console.error(e.stack))
}

setActive (isActive) {
if ((isActive && this.findOptions.findPattern) || !isActive) {
this.active = isActive
4 changes: 2 additions & 2 deletions lib/project/results-pane.js
Original file line number Diff line number Diff line change
@@ -201,7 +201,7 @@ class ResultsPaneView {
}

getTitle() {
return 'Project Find Results';
return 'Project search results';
}

getIconName() {
@@ -332,4 +332,4 @@ class ResultsPaneView {
}
}

module.exports.URI = "atom://find-and-replace/project-results";
module.exports.URI = "atom://find-and-replace/results";
3 changes: 3 additions & 0 deletions menus/find-and-replace.cson
Original file line number Diff line number Diff line change
@@ -10,6 +10,9 @@
{ 'label': 'Find in Project', 'command': 'project-find:show'}
{ 'label': 'Toggle Find in Project', 'command': 'project-find:toggle'}
{ 'type': 'separator' }
{ 'label': 'Find in Open Files', 'command': 'open-files-find:show'}
{ 'label': 'Toggle Find in Open Files', 'command': 'open-files-find:toggle'}
{ 'type': 'separator' }
{ 'label': 'Find All', 'command': 'find-and-replace:find-all'}
{ 'label': 'Find Next', 'command': 'find-and-replace:find-next'}
{ 'label': 'Find Previous', 'command': 'find-and-replace:find-previous'}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@
"license": "MIT",
"activationCommands": {
"atom-workspace": [
"open-files-find:show",
"open-files-find:toggle",
"project-find:show",
"project-find:toggle",
"project-find:show-in-current-directory",
@@ -34,6 +36,7 @@
"binary-search": "^1.3.3",
"etch": "0.9.3",
"fs-plus": "^3.0.0",
"scandal": "^3.1.0",
"temp": "^0.8.3",
"underscore-plus": "1.x"
},
1,392 changes: 1,392 additions & 0 deletions spec/open-files-find-view-spec.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions styles/find-and-replace.less
Original file line number Diff line number Diff line change
@@ -35,7 +35,9 @@ atom-workspace.find-visible {

// Both project and buffer FNR styles
.find-and-replace,
.open-files-find,
.preview-pane,
.open-files-find,
.project-find {
@min-width: 200px; // min width before it starts scrolling

@@ -216,6 +218,7 @@ atom-workspace.find-visible {
}

// Project find and replace
.open-files-find,
.project-find {
@project-input-width: 260px;
@project-block-width: 160px;