Rename extensions to packages

We now look at the `core.disabledPackages` config key. Rename the `src/extensions` directory to `src/packages`. The config object now talks about loading packages instead of extensions.
This commit is contained in:
Nathan Sobo
2012-12-18 20:03:21 -07:00
parent 37f0aa3f90
commit 4ce8583cb2
74 changed files with 15 additions and 15 deletions

View File

@@ -0,0 +1,67 @@
{
var CompositeCommand = require('command-panel/src/commands/composite-command')
var Substitution = require('command-panel/src/commands/substitution');
var ZeroAddress = require('command-panel/src/commands/zero-address');
var EofAddress = require('command-panel/src/commands/eof-address');
var LineAddress = require('command-panel/src/commands/line-address');
var AddressRange = require('command-panel/src/commands/address-range');
var DefaultAddressRange = require('command-panel/src/commands/default-address-range');
var CurrentSelectionAddress = require('command-panel/src/commands/current-selection-address')
var RegexAddress = require('command-panel/src/commands/regex-address')
var SelectAllMatches = require('command-panel/src/commands/select-all-matches')
var SelectAllMatchesInProject = require('command-panel/src/commands/select-all-matches-in-project')
}
start = _ commands:( selectAllMatchesInProject / textCommand ) {
return new CompositeCommand(commands);
}
textCommand = defaultAddress:defaultAddress? expressions:expression* {
if (defaultAddress) expressions.unshift(defaultAddress);
return expressions;
}
defaultAddress = !address {
return new DefaultAddressRange();
}
expression = _ expression:(address / substitution / selectAllMatches) {
return expression;
}
address = addressRange / primitiveAddress
addressRange = start:primitiveAddress? _ ',' _ end:address? {
if (!start) start = new ZeroAddress();
if (!end) end = new EofAddress();
return new AddressRange(start, end);
}
primitiveAddress
= '0' { return new ZeroAddress() }
/ '$' { return new EofAddress() }
/ '.' { return new CurrentSelectionAddress() }
/ lineNumber:integer { return new LineAddress(lineNumber) }
/ regexAddress
regexAddress
= reverse:'-'? '/' pattern:pattern '/'? { return new RegexAddress(pattern, reverse.length > 0)}
substitution
= "s" _ "/" find:pattern "/" replace:pattern "/" _ options:[g]* {
return new Substitution(find, eval("'" + replace + "'"), options);
}
selectAllMatches
= 'x' _ '/' pattern:pattern '/'? { return new SelectAllMatches(pattern) }
selectAllMatchesInProject
= 'X' _ 'x' _ '/' pattern:pattern '/'? { return [new SelectAllMatchesInProject(pattern)] }
pattern
= pattern:('\\/' / [^/])* { return pattern.join('') }
integer
= digits:[0-9]+ { return parseInt(digits.join('')); }
_ = " "*

View File

@@ -0,0 +1 @@
module.exports = require 'command-panel/src/command-panel'

View File

@@ -0,0 +1,446 @@
CommandInterpreter = require 'command-panel/src/command-interpreter'
Project = require 'project'
Buffer = require 'buffer'
EditSession = require 'edit-session'
describe "CommandInterpreter", ->
[project, interpreter, editSession, buffer, anchorCountBefore] = []
beforeEach ->
project = new Project(fixturesProject.resolve('dir/'))
interpreter = new CommandInterpreter(fixturesProject)
editSession = fixturesProject.buildEditSessionForPath('sample.js')
buffer = editSession.buffer
afterEach ->
editSession?.destroy()
expect(buffer.getAnchors().length).toBe 0
describe "addresses", ->
beforeEach ->
editSession.addSelectionForBufferRange([[7,0], [7,11]])
editSession.addSelectionForBufferRange([[8,0], [8,11]])
describe "a line address", ->
it "selects the specified line", ->
waitsForPromise -> interpreter.eval('4', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[3, 0], [4, 0]]
describe "0", ->
it "selects the zero-length string at the start of the file", ->
waitsForPromise -> interpreter.eval('0', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0,0], [0,0]]
interpreter.eval('0,1', editSession)
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0,0], [1,0]]
describe "$", ->
it "selects EOF", ->
waitsForPromise -> interpreter.eval('$', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[12,2], [12,2]]
waitsForPromise -> interpreter.eval('1,$', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0,0], [12,2]]
describe ".", ->
describe "when a single selection", ->
it 'maintains the current selection', ->
editSession.clearSelections()
waitsForPromise ->
editSession.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval('.', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[1,1], [2,2]]
waitsForPromise ->
editSession.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval('.,', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[1,1], [12,2]]
waitsForPromise ->
editSession.setSelectedBufferRange([[1,1], [2,2]])
interpreter.eval(',.', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[0,0], [2,2]]
describe "with multiple selections", ->
it "maintains the current selections", ->
preSelections = editSession.getSelections()
expect(preSelections.length).toBe 3
[preRange1, preRange2, preRange3] = preSelections.map (s) -> s.getScreenRange()
waitsForPromise -> interpreter.eval('.', editSession)
runs ->
selections = editSession.getSelections()
expect(selections.length).toBe 3
[selection1, selection2, selection3] = selections
expect(selection1.getScreenRange()).toEqual preRange1
expect(selection2.getScreenRange()).toEqual preRange2
expect(selection3.getScreenRange()).toEqual preRange3
describe "/regex/", ->
beforeEach ->
editSession.clearSelections()
it 'selects text matching regex after current selection', ->
waitsForPromise ->
editSession.setSelectedBufferRange([[4,16], [4,20]])
interpreter.eval('/pivot/', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[6,16], [6,21]]
it 'does not require the trailing slash', ->
waitsForPromise ->
editSession.setSelectedBufferRange([[4,16], [4,20]])
interpreter.eval('/pivot', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[6,16], [6,21]]
it "searches from the end of each selection in the buffer", ->
waitsForPromise ->
editSession.clearSelections()
editSession.setSelectedBufferRange([[4,16], [4,20]])
editSession.addSelectionForBufferRange([[1,16], [2,20]])
expect(editSession.getSelections().length).toBe 2
interpreter.eval('/pivot', editSession)
runs ->
selections = editSession.getSelections()
expect(selections.length).toBe 2
expect(selections[0].getBufferRange()).toEqual [[3,8], [3,13]]
expect(selections[1].getBufferRange()).toEqual [[6,16], [6,21]]
it "wraps around to the beginning of the buffer, but doesn't infinitely loop if no matches are found", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[10, 0], [10,3]])
interpreter.eval('/pivot', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
waitsForPromise ->
interpreter.eval('/mike tyson', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
it "searches in reverse when prefixed with a -", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 16], [6, 22]])
interpreter.eval('-/pivot', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[3,8], [3,13]]
it "removes folds that contain the selections", ->
waitsForPromise ->
editSession.createFold(5, 6)
editSession.createFold(10, 11)
editSession.setSelectedBufferRange([[4,16], [4,20]])
interpreter.eval('/pivot/', editSession)
runs ->
expect(editSession.getSelection().getBufferRange()).toEqual [[6,16], [6,21]]
expect(editSession.lineForScreenRow(5).fold).toBeUndefined()
expect(editSession.lineForScreenRow(10).fold).toBeDefined()
it "is case-insentive when the pattern contains no non-escaped uppercase letters (behavior copied from vim)", ->
waitsForPromise ->
interpreter.eval('/array', editSession)
runs ->
expect(interpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/array/i"
waitsForPromise ->
interpreter.eval('/a\\Sray', editSession)
runs ->
expect(interpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/a\\Sray/i"
it "allows the regex to contain an escaped forward slash", ->
buffer.setText "hey/baby"
waitsForPromise ->
interpreter.eval('/y\\/b/', editSession)
runs ->
expect(editSession.getSelectedText()).toBe "y/b"
it "does not push to the undo stack (since the buffer is not modified)", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[4,16], [4,20]])
interpreter.eval('/pivot/', editSession)
runs ->
selectedRangeBeforeUndo = editSession.getSelection().getBufferRange()
editSession.undo()
expect(editSession.getSelection().getBufferRange()).toEqual selectedRangeBeforeUndo
describe "when no match is found", ->
it "it returns an error messages", ->
errorMessages = null
waitsForPromise ->
interpreter.eval('/garbage!', editSession).done (results) -> { errorMessages } = results
runs ->
expect(errorMessages.length).toBe 1
waitsForPromise ->
interpreter.eval('/Array', editSession)
runs ->
expect(interpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/Array/"
describe "address range", ->
describe "when two addresses are specified", ->
it "selects from the begining of the left address to the end of the right address", ->
waitsForPromise -> interpreter.eval('4,7', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[3, 0], [7, 0]]
describe "when the left address is unspecified", ->
it "selects from the begining of buffer to the end of the right address", ->
waitsForPromise -> interpreter.eval(',7', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0, 0], [7, 0]]
describe "when the right address is unspecified", ->
it "selects from the begining of left address to the end file", ->
waitsForPromise -> interpreter.eval('4,', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[3, 0], [12, 2]]
describe "when the neither address is specified", ->
it "selects the entire file", ->
waitsForPromise -> interpreter.eval(',', editSession)
runs ->
expect(editSession.getSelections().length).toBe 1
expect(editSession.getSelection().getBufferRange()).toEqual [[0, 0], [12, 2]]
describe "x/regex/", ->
it "sets the current selection to every match of the regex in the current selection", ->
waitsForPromise -> interpreter.eval('6,7 x/current/', editSession)
runs ->
selections = editSession.getSelections()
expect(selections.length).toBe 4
expect(selections[0].getBufferRange()).toEqual [[5,6], [5,13]]
expect(selections[1].getBufferRange()).toEqual [[6,6], [6,13]]
expect(selections[2].getBufferRange()).toEqual [[6,34], [6,41]]
expect(selections[3].getBufferRange()).toEqual [[6,56], [6,63]]
describe "when there is no address range is given", ->
describe "when there is no text selection", ->
it "uses the entire file as the address range", ->
waitsForPromise ->
editSession.clearSelections()
interpreter.eval('x/return', editSession)
runs ->
expect(editSession.getSelectedBufferRanges()).toEqual [
[[2,27],[2,33]]
[[8,4], [8,10]]
[[11,2],[11,8]]
]
describe "when text is selected", ->
it "uses the selection as the address range", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[2, 0], [9, 0]])
interpreter.eval('x/return', editSession)
runs ->
expect(editSession.getSelectedBufferRanges()).toEqual [
[[2,27],[2,33]]
[[8,4], [8,10]]
]
describe "when matching /$/", ->
it "matches the end of each line in the selected region", ->
waitsForPromise -> interpreter.eval('6,8 x/$/', editSession)
runs ->
cursors = editSession.getCursors()
expect(cursors.length).toBe 3
expect(cursors[0].getBufferPosition()).toEqual [5, 30]
expect(cursors[1].getBufferPosition()).toEqual [6, 65]
expect(cursors[2].getBufferPosition()).toEqual [7, 5]
describe "when text is initially selected", ->
it "loops through current selections and selects text matching the regex", ->
waitsForPromise ->
editSession.setSelectedBufferRange [[3,0], [3,62]]
editSession.addSelectionForBufferRange [[6,0], [6,65]]
interpreter.eval('x/current', editSession)
runs ->
selections = editSession.getSelections()
expect(selections.length).toBe 4
expect(selections[0].getBufferRange()).toEqual [[3,31], [3,38]]
expect(selections[1].getBufferRange()).toEqual [[6,6], [6,13]]
expect(selections[2].getBufferRange()).toEqual [[6,34], [6,41]]
expect(selections[3].getBufferRange()).toEqual [[6,56], [6,63]]
describe "substitution", ->
describe "when there is no address range is given", ->
describe "when there is no text selection", ->
it "uses the entire file as the address range", ->
waitsForPromise ->
editSession.clearSelections()
interpreter.eval('s/current/foo/g', editSession)
runs ->
expect(buffer.lineForRow(3)).toBe ' var pivot = items.shift(), foo, left = [], right = [];'
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(foo);'
describe "when text is selected", ->
it "uses the selection as the address range", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/g', editSession)
runs ->
expect(buffer.lineForRow(3)).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(current);'
describe "when not global", ->
describe "when there is a single selection", ->
it "performs a single substitution within the current selection", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/', editSession)
runs ->
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(current) : right.push(current);'
describe "when there are multiple selections", ->
it "performs a single substitutions within each of the selections", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[5, 0], [5, 20]])
editSession.addSelectionForBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/', editSession)
runs ->
expect(buffer.lineForRow(5)).toBe ' foo = items.shift();'
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(current) : right.push(current);'
describe "when global", ->
it "performs a multiple substitutions within the current selection as a batch that can be undone in a single operation", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/g', editSession)
runs ->
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(current);'
buffer.undo()
expect(buffer.getText()).not.toContain('foo')
describe "when prefixed with an address", ->
it "only makes substitutions within given lines", ->
waitsForPromise -> interpreter.eval('4,6s/ /!/g', editSession)
runs ->
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;'
expect(buffer.lineForRow(3)).toBe '!!!!var!pivot!=!items.shift(),!current,!left!=![],!right!=![];'
expect(buffer.lineForRow(4)).toBe '!!!!while(items.length!>!0)!{'
expect(buffer.lineForRow(5)).toBe '!!!!!!current!=!items.shift();'
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);'
describe "when matching $", ->
it "matches the end of each line and avoids infinitely looping on a zero-width match", ->
waitsForPromise -> interpreter.eval(',s/$/!!!/g', editSession)
runs ->
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {!!!'
expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;!!!'
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);!!!'
expect(buffer.lineForRow(12)).toBe '};!!!'
describe "when matching ^", ->
it "matches the beginning of each line and avoids infinitely looping on a zero-width match", ->
waitsForPromise -> interpreter.eval(',s/^/!!!/g', editSession)
runs ->
expect(buffer.lineForRow(0)).toBe '!!!var quicksort = function () {'
expect(buffer.lineForRow(2)).toBe '!!! if (items.length <= 1) return items;'
expect(buffer.lineForRow(6)).toBe '!!! current < pivot ? left.push(current) : right.push(current);'
expect(buffer.lineForRow(12)).toBe '!!!};'
describe "when there are multiple selections", ->
it "performs a multiple substitutions within each of the selections", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[5, 0], [5, 20]])
editSession.addSelectionForBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/current/foo/g', editSession)
runs ->
expect(buffer.lineForRow(5)).toBe ' foo = items.shift();'
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(current);'
describe "when prefixed with an address", ->
it "restores the original selections upon completion if it is the last command", ->
waitsForPromise ->
editSession.setSelectedBufferRanges([[[5, 0], [5, 20]], [[6, 0], [6, 44]]])
interpreter.eval(',s/current/foo/g', editSession)
runs ->
expect(editSession.getSelectedBufferRanges()).toEqual [[[5, 0], [5, 16]], [[6, 0], [6, 36]]]
it "does nothing if there are no matches", ->
waitsForPromise ->
editSession.setSelectedBufferRange([[6, 0], [6, 44]])
interpreter.eval('s/not-in-text/foo/', editSession)
runs ->
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);'
it "properly handles escaped text in the replacement text", ->
waitsForPromise ->
interpreter.eval('s/ /\\t/g', editSession)
runs ->
expect(buffer.lineForRow(6)).toBe '\t\t\tcurrent < pivot ? left.push(current) : right.push(current);'
describe "X x/regex/", ->
it "returns selection operations for all regex matches in all the project's files", ->
editSession.destroy()
project = new Project(fixturesProject.resolve('dir/'))
interpreter = new CommandInterpreter(project)
operationsToPreview = null
waitsForPromise ->
interpreter.eval("X x/a+/").done (result) -> {operationsToPreview} = result
runs ->
expect(operationsToPreview.length).toBeGreaterThan 3
for operation in operationsToPreview
editSession = project.buildEditSessionForPath(operation.getPath())
editSession.setSelectedBufferRange(operation.execute(editSession))
expect(editSession.getSelectedText()).toMatch /a+/
editSession.destroy()
operation.destroy()
editSession = null
describe "nested commands", ->
describe "/regex/ /regex", ->
it "returns an error message if the last regex has no matches", ->
previousSelections = null
errorMessages = null
waitsForPromise ->
previousSelections = editSession.getSelectedBufferRanges()
interpreter.eval('/sort/ /no match', editSession).done (results) -> { errorMessages } = results
runs ->
expect(errorMessages.length).toBe 1

View File

@@ -0,0 +1,458 @@
RootView = require 'root-view'
CommandPanel = require 'command-panel'
_ = require 'underscore'
describe "CommandPanel", ->
[rootView, editor, buffer, commandPanel, project] = []
beforeEach ->
rootView = new RootView
rootView.open(require.resolve 'fixtures/sample.js')
rootView.enableKeymap()
project = rootView.project
editor = rootView.getActiveEditor()
buffer = editor.activeEditSession.buffer
atom.loadPackage('command-panel')
commandPanel = CommandPanel.instance
commandPanel.history = []
commandPanel.historyIndex = 0
afterEach ->
rootView.deactivate()
describe "serialization", ->
it "preserves the command panel's mini-editor text, visibility, focus, and history across reloads", ->
rootView.attachToDom()
rootView.trigger 'command-panel:toggle'
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
commandPanel.execute('/.')
expect(commandPanel.history.length).toBe(1)
expect(commandPanel.history[0]).toBe('/.')
expect(commandPanel.historyIndex).toBe(1)
rootView.trigger 'command-panel:toggle'
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
commandPanel.miniEditor.insertText 'abc'
rootView2 = RootView.deserialize(rootView.serialize())
rootView.deactivate()
rootView2.attachToDom()
commandPanel = rootView2.activateExtension(CommandPanel)
expect(rootView2.find('.command-panel')).toExist()
expect(commandPanel.miniEditor.getText()).toBe 'abc'
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
expect(commandPanel.history.length).toBe(1)
expect(commandPanel.history[0]).toBe('/.')
expect(commandPanel.historyIndex).toBe(1)
rootView2.focus()
expect(commandPanel.miniEditor.isFocused).toBeFalsy()
rootView3 = RootView.deserialize(rootView2.serialize())
rootView2.deactivate()
rootView3.attachToDom()
commandPanel = rootView3.activateExtension(CommandPanel)
expect(commandPanel.miniEditor.isFocused).toBeFalsy()
rootView3.deactivate()
it "only retains the configured max serialized history size", ->
rootView.attachToDom()
commandPanel.maxSerializedHistorySize = 2
commandPanel.execute('/test1')
commandPanel.execute('/test2')
commandPanel.execute('/test3')
expect(commandPanel.history.length).toBe(3)
expect(commandPanel.history[0]).toBe('/test1')
expect(commandPanel.history[1]).toBe('/test2')
expect(commandPanel.history[2]).toBe('/test3')
expect(commandPanel.historyIndex).toBe(3)
rootView2 = RootView.deserialize(rootView.serialize())
rootView.deactivate()
rootView2.attachToDom()
commandPanel = rootView2.activateExtension(CommandPanel)
expect(commandPanel.history.length).toBe(2)
expect(commandPanel.history[0]).toBe('/test2')
expect(commandPanel.history[1]).toBe('/test3')
expect(commandPanel.historyIndex).toBe(2)
rootView2.deactivate()
describe "when core:close is triggered on the command panel", ->
it "detaches the command panel, focuses the RootView and does not bubble the core:close event", ->
commandPanel.attach()
rootViewCloseHandler = jasmine.createSpy('rootViewCloseHandler')
rootView.on 'core:close', rootViewCloseHandler
spyOn(rootView, 'focus')
commandPanel.trigger('core:close')
expect(rootView.focus).toHaveBeenCalled()
expect(rootViewCloseHandler).not.toHaveBeenCalled()
expect(commandPanel.hasParent()).toBeFalsy()
describe "when command-panel:toggle is triggered on the root view", ->
beforeEach ->
rootView.attachToDom()
describe "when the command panel is visible", ->
beforeEach ->
commandPanel.attach()
describe "when the mini editor is focused", ->
it "closes the command panel", ->
expect(commandPanel.miniEditor.hiddenInput).toMatchSelector ':focus'
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeFalsy()
describe "when the mini editor is not focused", ->
it "focuses the mini editor", ->
rootView.focus()
expect(commandPanel.miniEditor.hiddenInput).not.toMatchSelector ':focus'
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeTruthy()
expect(commandPanel.miniEditor.hiddenInput).toMatchSelector ':focus'
describe "when the command panel is not visible", ->
it "shows and focuses the command panel", ->
expect(commandPanel.hasParent()).toBeFalsy()
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeTruthy()
describe "when command-panel:toggle-preview is triggered on the root view", ->
beforeEach ->
rootView.attachToDom()
describe "when the preview list is/was previously visible", ->
beforeEach ->
rootView.trigger 'command-panel:toggle'
waitsForPromise -> commandPanel.execute('X x/a+/')
describe "when the command panel is visible", ->
beforeEach ->
expect(commandPanel.hasParent()).toBeTruthy()
describe "when the preview list is visible", ->
beforeEach ->
expect(commandPanel.previewList).toBeVisible()
describe "when the preview list is focused", ->
it "hides the command panel", ->
expect(commandPanel.previewList).toMatchSelector(':focus')
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.hasParent()).toBeFalsy()
describe "when the preview list is not focused", ->
it "focuses the preview list", ->
commandPanel.miniEditor.focus()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toMatchSelector(':focus')
describe "when the preview list is not visible", ->
beforeEach ->
commandPanel.miniEditor.focus()
rootView.trigger 'command-panel:toggle'
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeTruthy()
expect(commandPanel.previewList).toBeHidden()
it "shows and focuses the preview list", ->
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toBeVisible()
expect(commandPanel.previewList).toMatchSelector(':focus')
describe "when the command panel is not visible", ->
it "shows the command panel and the preview list, and focuses the preview list", ->
commandPanel.miniEditor.focus()
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeFalsy()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.hasParent()).toBeTruthy()
expect(commandPanel.previewList).toBeVisible()
expect(commandPanel.previewList).toMatchSelector(':focus')
describe "when the preview list has never been opened", ->
describe "when the command panel is visible", ->
beforeEach ->
rootView.trigger 'command-panel:toggle'
expect(commandPanel.hasParent()).toBeTruthy()
describe "when the mini editor is focused", ->
it "retains focus on the mini editor and does not show the preview list", ->
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toBeHidden()
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
describe "when the mini editor is not focused", ->
it "focuses the mini editor and does not show the preview list", ->
rootView.focus()
rootView.trigger 'command-panel:toggle-preview'
expect(commandPanel.previewList).toBeHidden()
expect(commandPanel.miniEditor.isFocused).toBeTruthy()
describe "when the command panel is not visible", ->
it "shows the command panel and focuses the mini editor, but does not show the preview list", ->
describe "when tool-panel:unfocus is triggered on the command panel", ->
it "returns focus to the root view but does not hide the command panel", ->
rootView.attachToDom()
commandPanel.attach()
expect(commandPanel.miniEditor.hiddenInput).toMatchSelector ':focus'
commandPanel.trigger 'tool-panel:unfocus'
expect(commandPanel.hasParent()).toBeTruthy()
expect(commandPanel.miniEditor.hiddenInput).not.toMatchSelector ':focus'
describe "when command-panel:repeat-relative-address is triggered on the root view", ->
it "repeats the last search command if there is one", ->
rootView.trigger 'command-panel:repeat-relative-address'
editor.setCursorScreenPosition([4, 0])
commandPanel.execute("/current")
expect(editor.getSelection().getBufferRange()).toEqual [[5,6], [5,13]]
rootView.trigger 'command-panel:repeat-relative-address'
expect(editor.getSelection().getBufferRange()).toEqual [[6,6], [6,13]]
commandPanel.execute('s/r/R/g')
rootView.trigger 'command-panel:repeat-relative-address'
expect(editor.getSelection().getBufferRange()).toEqual [[6,34], [6,41]]
commandPanel.execute('0')
commandPanel.execute('/sort/ s/r/R/') # this contains a substitution... won't be repeated
rootView.trigger 'command-panel:repeat-relative-address'
expect(editor.getSelection().getBufferRange()).toEqual [[3,31], [3,38]]
describe "when command-panel:repeat-relative-address-in-reverse is triggered on the root view", ->
it "it repeats the last relative address in the reverse direction", ->
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
editor.setCursorScreenPosition([6, 0])
commandPanel.execute("/current")
expect(editor.getSelection().getBufferRange()).toEqual [[6,6], [6,13]]
rootView.trigger 'command-panel:repeat-relative-address-in-reverse'
expect(editor.getSelection().getBufferRange()).toEqual [[5,6], [5,13]]
describe "when command-panel:set-selection-as-regex-address is triggered on the root view", ->
it "sets the @lastRelativeAddress to a RegexAddress of the current selection", ->
rootView.open(require.resolve('fixtures/sample.js'))
rootView.getActiveEditor().setSelectedBufferRange([[1,21],[1,28]])
commandInterpreter = commandPanel.commandInterpreter
expect(commandInterpreter.lastRelativeAddress).toBeUndefined()
rootView.trigger 'command-panel:set-selection-as-regex-address'
expect(commandInterpreter.lastRelativeAddress.subcommands.length).toBe 1
expect(commandInterpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/\\(items\\)/i"
describe "when command-panel:find-in-file is triggered on an editor", ->
it "pre-populates the command panel's editor with / and moves the cursor to the last column", ->
spyOn(commandPanel, 'attach').andCallThrough()
commandPanel.miniEditor.setText("foo")
commandPanel.miniEditor.setCursorBufferPosition([0, 0])
rootView.getActiveEditor().trigger "command-panel:find-in-file"
expect(commandPanel.attach).toHaveBeenCalled()
expect(commandPanel.parent).not.toBeEmpty()
expect(commandPanel.miniEditor.getText()).toBe "/"
expect(commandPanel.miniEditor.getCursorBufferPosition()).toEqual [0, 1]
describe "when command-panel:find-in-project is triggered on the root view", ->
it "pre-populates the command panel's editor with Xx/ and moves the cursor to the last column", ->
spyOn(commandPanel, 'attach').andCallThrough()
commandPanel.miniEditor.setText("foo")
commandPanel.miniEditor.setCursorBufferPosition([0, 0])
rootView.trigger "command-panel:find-in-project"
expect(commandPanel.attach).toHaveBeenCalled()
expect(commandPanel.parent).not.toBeEmpty()
expect(commandPanel.miniEditor.getText()).toBe "Xx/"
expect(commandPanel.miniEditor.getCursorBufferPosition()).toEqual [0, 3]
describe "when return is pressed on the panel's editor", ->
describe "if the command has an immediate effect", ->
it "executes it immediately on the current buffer", ->
rootView.trigger 'command-panel:toggle'
commandPanel.miniEditor.insertText ',s/sort/torta/g'
commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
expect(buffer.lineForRow(0)).toMatch /quicktorta/
expect(buffer.lineForRow(1)).toMatch /var torta/
describe "when the command returns operations to be previewed", ->
beforeEach ->
rootView.attachToDom()
editor.remove()
rootView.trigger 'command-panel:toggle'
waitsForPromise -> commandPanel.execute('X x/a+/')
it "displays and focuses the operation preview list", ->
expect(commandPanel).toBeVisible()
expect(commandPanel.previewList).toBeVisible()
expect(commandPanel.previewList).toMatchSelector ':focus'
previewItem = commandPanel.previewList.find("li:contains(dir/a):first")
expect(previewItem.find('.path').text()).toBe "dir/a"
expect(previewItem.find('.preview').text()).toBe "aaa bbb"
expect(previewItem.find('.preview > .match').text()).toBe "aaa"
rootView.trigger 'command-panel:toggle-preview' # ensure we can close panel without problems
expect(commandPanel).toBeHidden()
it "destroys previously previewed operations if there are any", ->
waitsForPromise -> commandPanel.execute('X x/b+/')
# there shouldn't be any dangling operations after this
describe "if the command is malformed", ->
it "adds and removes an error class to the command panel and does not close it", ->
rootView.trigger 'command-panel:toggle'
commandPanel.miniEditor.insertText 'garbage-command!!'
commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
expect(commandPanel.parent()).toExist()
expect(commandPanel).toHaveClass 'error'
advanceClock 400
expect(commandPanel).not.toHaveClass 'error'
describe "if the command returns an error message", ->
beforeEach ->
rootView.attachToDom()
rootView.trigger 'command-panel:toggle'
commandPanel.miniEditor.insertText '/garbage'
expect(commandPanel.errorMessages).not.toBeVisible()
commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
it "adds and removes an error class to the command panel and displays the error message", ->
expect(commandPanel).toBeVisible()
expect(commandPanel.errorMessages).toBeVisible()
expect(commandPanel).toHaveClass 'error'
it "removes the error message when the command-panel is toggled", ->
rootView.trigger 'command-panel:toggle' # off
rootView.trigger 'command-panel:toggle' # on
expect(commandPanel).toBeVisible()
expect(commandPanel.errorMessages).not.toBeVisible()
describe "when the command contains an escaped charachter", ->
it "executes the command with the escaped character (instead of as a backslash followed by the character)", ->
rootView.trigger 'command-panel:toggle'
editSession = rootView.open(require.resolve 'fixtures/sample-with-tabs.coffee')
editor.edit(editSession)
commandPanel.miniEditor.setText "/\\tsell"
commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
expect(editor.getSelectedBufferRange()).toEqual [[3,1],[3,6]]
describe "when move-up and move-down are triggerred on the editor", ->
it "navigates forward and backward through the command history", ->
commandPanel.execute 's/war/peace/g'
commandPanel.execute 's/twinkies/wheatgrass/g'
rootView.trigger 'command-panel:toggle'
commandPanel.miniEditor.trigger 'core:move-up'
expect(commandPanel.miniEditor.getText()).toBe 's/twinkies/wheatgrass/g'
commandPanel.miniEditor.trigger 'core:move-up'
expect(commandPanel.miniEditor.getText()).toBe 's/war/peace/g'
commandPanel.miniEditor.trigger 'core:move-up'
expect(commandPanel.miniEditor.getText()).toBe 's/war/peace/g'
commandPanel.miniEditor.trigger 'core:move-down'
expect(commandPanel.miniEditor.getText()).toBe 's/twinkies/wheatgrass/g'
commandPanel.miniEditor.trigger 'core:move-down'
expect(commandPanel.miniEditor.getText()).toBe ''
describe "when the preview list is focused with search operations", ->
previewList = null
beforeEach ->
previewList = commandPanel.previewList
rootView.trigger 'command-panel:toggle'
waitsForPromise -> commandPanel.execute('X x/a/')
describe "when move-down and move-up are triggered on the preview list", ->
it "selects the next/previous operation (if there is one), and scrolls the list if needed", ->
rootView.attachToDom()
expect(previewList.find('li:eq(0)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[0]
previewList.trigger 'core:move-up'
expect(previewList.find('li:eq(0)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[0]
previewList.trigger 'core:move-down'
expect(previewList.find('li:eq(1)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[1]
previewList.trigger 'core:move-down'
expect(previewList.find('li:eq(2)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[2]
previewList.trigger 'core:move-up'
expect(previewList.find('li:eq(1)')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[1]
_.times previewList.getOperations().length, -> previewList.trigger 'core:move-down'
expect(previewList.find('li:last')).toHaveClass 'selected'
expect(previewList.getSelectedOperation()).toBe _.last(previewList.getOperations())
expect(previewList.scrollBottom()).toBeCloseTo previewList.prop('scrollHeight'), -1
_.times previewList.getOperations().length, -> previewList.trigger 'core:move-up'
it "doesn't bubble up the event and the command panel text doesn't change", ->
rootView.attachToDom()
commandPanel.miniEditor.setText "command"
previewList.focus()
previewList.trigger 'core:move-up'
expect(previewList.find('li:eq(0)')).toHaveClass 'selected'
expect(commandPanel.miniEditor.getText()).toBe 'command'
previewList.trigger 'core:move-down'
expect(previewList.find('li:eq(1)')).toHaveClass 'selected'
expect(commandPanel.miniEditor.getText()).toBe 'command'
describe "when core:confirm is triggered on the preview list", ->
it "opens the operation's buffer, selects and scrolls to the search result, and focuses the active editor", ->
rootView.height(200)
rootView.attachToDom()
waitsForPromise -> commandPanel.execute('X x/apply/') # use apply because it is at the end of the file
runs ->
spyOn(rootView, 'focus')
executeHandler = jasmine.createSpy('executeHandler')
commandPanel.on 'core:confirm', executeHandler
_.times 4, -> previewList.trigger 'core:move-down'
operation = previewList.getSelectedOperation()
previewList.trigger 'core:confirm'
editSession = rootView.getActiveEditSession()
expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath())
expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
expect(editor.isScreenRowVisible(editor.getCursorScreenRow())).toBeTruthy()
expect(rootView.focus).toHaveBeenCalled()
expect(executeHandler).not.toHaveBeenCalled()
describe "when an operation in the preview list is clicked", ->
it "opens the operation's buffer, selects the search result, and focuses the active editor", ->
spyOn(rootView, 'focus')
operation = previewList.getOperations()[4]
previewList.find('li:eq(4) span').mousedown()
expect(previewList.getSelectedOperation()).toBe operation
editSession = rootView.getActiveEditSession()
expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath())
expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange()
expect(rootView.focus).toHaveBeenCalled()

View File

@@ -0,0 +1,18 @@
fs = require 'fs'
PEG = require 'pegjs'
module.exports =
class CommandInterpreter
constructor: (@project) ->
@parser = PEG.buildParser(fs.read(require.resolve 'command-panel/commands.pegjs'))
eval: (string, activeEditSession) ->
compositeCommand = @parser.parse(string)
@lastRelativeAddress = compositeCommand if compositeCommand.isRelativeAddress()
compositeCommand.execute(@project, activeEditSession)
repeatRelativeAddress: (activeEditSession) ->
@lastRelativeAddress?.execute(@project, activeEditSession)
repeatRelativeAddressInReverse: (activeEditSession) ->
@lastRelativeAddress?.reverse().execute(@project, activeEditSession)

View File

@@ -0,0 +1,156 @@
{View, $$, $$$} = require 'space-pen'
CommandInterpreter = require 'command-panel/src/command-interpreter'
RegexAddress = require 'command-panel/src/commands/regex-address'
CompositeCommand = require 'command-panel/src/commands/composite-command'
PreviewList = require 'command-panel/src/preview-list'
Editor = require 'editor'
{SyntaxError} = require('pegjs').parser
_ = require 'underscore'
module.exports =
class CommandPanel extends View
@activate: (rootView, state) ->
requireStylesheet 'command-panel.css'
if state?
@instance = CommandPanel.deserialize(state, rootView)
else
@instance = new CommandPanel(rootView)
@deactivate: ->
@instance.destroy()
@serialize: ->
text: @instance.miniEditor.getText()
visible: @instance.hasParent()
miniEditorFocused: @instance.miniEditor.isFocused
history: @instance.history[-@instance.maxSerializedHistorySize..]
@deserialize: (state, rootView) ->
commandPanel = new CommandPanel(rootView, state.history)
commandPanel.attach(state.text, focus: false) if state.visible
commandPanel.miniEditor.focus() if state.miniEditorFocused
commandPanel
@content: (rootView) ->
@div class: 'command-panel tool-panel', =>
@subview 'previewList', new PreviewList(rootView)
@ul class: 'error-messages', outlet: 'errorMessages'
@div class: 'prompt-and-editor', =>
@div ':', class: 'prompt', outlet: 'prompt'
@subview 'miniEditor', new Editor(mini: true)
commandInterpreter: null
history: null
historyIndex: 0
maxSerializedHistorySize: 100
initialize: (@rootView, @history) ->
@commandInterpreter = new CommandInterpreter(@rootView.project)
@history ?= []
@historyIndex = @history.length
@command 'tool-panel:unfocus', => @rootView.focus()
@command 'core:close', => @detach(); false
@command 'core:confirm', => @execute()
@rootView.command 'command-panel:toggle', => @toggle()
@rootView.command 'command-panel:toggle-preview', => @togglePreview()
@rootView.command 'command-panel:find-in-file', => @attach("/")
@rootView.command 'command-panel:find-in-project', => @attach("Xx/")
@rootView.command 'command-panel:repeat-relative-address', => @repeatRelativeAddress()
@rootView.command 'command-panel:repeat-relative-address-in-reverse', => @repeatRelativeAddressInReverse()
@rootView.command 'command-panel:set-selection-as-regex-address', => @setSelectionAsLastRelativeAddress()
@command 'core:move-up', => @navigateBackwardInHistory()
@command 'core:move-down', => @navigateForwardInHistory()
@previewList.hide()
@errorMessages.hide()
destroy: ->
@previewList.destroy()
toggle: ->
if @miniEditor.isFocused
@detach()
@rootView.focus()
else
@attach() unless @hasParent()
@miniEditor.focus()
togglePreview: ->
if @previewList.is(':focus')
@previewList.hide()
@detach()
@rootView.focus()
else
@attach() unless @hasParent()
if @previewList.hasOperations()
@previewList.show().focus()
else
@miniEditor.focus()
attach: (text='', options={}) ->
@errorMessages.hide()
focus = options.focus ? true
@rootView.vertical.append(this)
@miniEditor.focus() if focus
@miniEditor.setText(text)
@miniEditor.setCursorBufferPosition([0, Infinity])
detach: ->
@rootView.focus()
@previewList.hide()
super
escapedCommand: ->
@miniEditor.getText()
execute: (command=@escapedCommand())->
@errorMessages.empty()
try
@commandInterpreter.eval(command, @rootView.getActiveEditSession()).done ({operationsToPreview, errorMessages}) =>
@history.push(command)
@historyIndex = @history.length
if errorMessages.length > 0
@flashError()
@errorMessages.show()
@errorMessages.append $$ ->
@li errorMessage for errorMessage in errorMessages
else if operationsToPreview?.length
@previewList.populate(operationsToPreview)
@previewList.focus()
else
@detach()
catch error
if error.name is "SyntaxError"
@flashError()
return
else
throw error
navigateBackwardInHistory: ->
return if @historyIndex == 0
@historyIndex--
@miniEditor.setText(@history[@historyIndex])
navigateForwardInHistory: ->
return if @historyIndex == @history.length
@historyIndex++
@miniEditor.setText(@history[@historyIndex] or '')
repeatRelativeAddress: ->
@commandInterpreter.repeatRelativeAddress(@rootView.getActiveEditSession())
repeatRelativeAddressInReverse: ->
@commandInterpreter.repeatRelativeAddressInReverse(@rootView.getActiveEditSession())
setSelectionAsLastRelativeAddress: ->
selection = @rootView.getActiveEditor().getSelectedText()
regex = _.escapeRegExp(selection)
@commandInterpreter.lastRelativeAddress = new CompositeCommand([new RegexAddress(regex)])

View File

@@ -0,0 +1,12 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
module.exports =
class AddressRange extends Address
constructor: (@startAddress, @endAddress) ->
getRange: (buffer, range) ->
new Range(@startAddress.getRange(buffer, range).start, @endAddress.getRange(buffer, range).end)
isRelative: ->
@startAddress.isRelative() and @endAddress.isRelative()

View File

@@ -0,0 +1,20 @@
Command = require 'command-panel/src/commands/command'
Operation = require 'command-panel/src/operation'
$ = require 'jquery'
module.exports =
class Address extends Command
compile: (project, buffer, ranges) ->
deferred = $.Deferred()
deferred.resolve ranges.map (range) =>
newRange = @getRange(buffer, range)
new Operation
project: project
buffer: buffer
bufferRange: newRange
errorMessage: @errorMessage
deferred.promise()
isAddress: -> true

View File

@@ -0,0 +1,9 @@
_ = require 'underscore'
module.exports =
class Command
isAddress: -> false
errorMessage: null
preserveSelections: false
previewOperations: false

View File

@@ -0,0 +1,63 @@
_ = require 'underscore'
$ = require 'jquery'
module.exports =
class CompositeCommand
constructor: (@subcommands) ->
execute: (project, editSession) ->
currentRanges = editSession?.getSelectedBufferRanges()
@executeCommands(@subcommands, project, editSession, currentRanges)
executeCommands: (commands, project, editSession, ranges) ->
deferred = $.Deferred()
[currentCommand, remainingCommands...] = commands
currentCommand.compile(project, editSession?.buffer, ranges).done (operations) =>
if remainingCommands.length
errorMessages = @errorMessagesForOperations(operations)
nextRanges = operations.map (operation) -> operation.getBufferRange()
operations.forEach (operation) -> operation.destroy()
@executeCommands(remainingCommands, project, editSession, nextRanges).done ({errorMessages: moreErrorMessages})->
errorMessages.push(moreErrorMessages...) if moreErrorMessages
deferred.resolve({errorMessages})
else
errorMessages = @errorMessagesForOperations(operations)
if currentCommand.previewOperations
deferred.resolve({operationsToPreview: operations, errorMessages})
else
bufferRanges = []
errorMessages = @errorMessagesForOperations(operations)
executeOperations = ->
for operation in operations
bufferRange = operation.execute(editSession)
bufferRanges.push(bufferRange) if bufferRange
operation.destroy()
if bufferRanges.length and not currentCommand.preserveSelections
editSession.setSelectedBufferRanges(bufferRanges, autoscroll: true)
operationsWillChangeBuffer = _.detect(operations, (operation) -> operation.newText)
if operationsWillChangeBuffer
editSession.transact(executeOperations)
else
executeOperations()
deferred.resolve({errorMessages})
deferred.promise()
errorMessagesForOperations: (operations) ->
operationsWithErrorMessages = operations.filter (operation) -> operation.errorMessage?
operationsWithErrorMessages.map (operation) -> operation.errorMessage
reverse: ->
new CompositeCommand(@subcommands.map (command) -> command.reverse())
isRelativeAddress: ->
_.all(@subcommands, (command) -> command.isAddress() and command.isRelative())

View File

@@ -0,0 +1,9 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
module.exports =
class CurrentSelectionAddress extends Address
getRange: (buffer, range) ->
range
isRelative: -> true

View File

@@ -0,0 +1,12 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
module.exports =
class DefaultAddressRange extends Address
getRange: (buffer, range)->
if range.isEmpty()
buffer.getRange()
else
range
isRelative: -> false

View File

@@ -0,0 +1,10 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
module.exports =
class EofAddress extends Address
getRange: (buffer, range) ->
eof = buffer.getEofPosition()
new Range(eof, eof)
isRelative: -> false

View File

@@ -0,0 +1,12 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
module.exports =
class LineAddress extends Address
constructor: (lineNumber) ->
@row = lineNumber - 1
getRange: ->
new Range([@row, 0], [@row + 1, 0])
isRelative: -> false

View File

@@ -0,0 +1,47 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
module.exports =
class RegexAddress extends Address
regex: null
reverse: null
constructor: (@pattern, isReversed, options) ->
flags = ""
pattern = pattern.source if pattern.source
patternContainsCapitalLetter = /(^|[^\\])[A-Z]/.test(pattern)
flags += "i" unless patternContainsCapitalLetter
@isReversed = isReversed
@regex = new RegExp(pattern, flags)
getRange: (buffer, range) ->
rangeBefore = new Range([0, 0], range.start)
rangeAfter = new Range(range.end, buffer.getEofPosition())
rangeToSearch = if @isReversed then rangeBefore else rangeAfter
rangeToReturn = null
scanMethodName = if @isReversed then "backwardsScanInRange" else "scanInRange"
buffer[scanMethodName] @regex, rangeToSearch, (match, range) ->
rangeToReturn = range
if not rangeToReturn
rangeToSearch = if @isReversed then rangeAfter else rangeBefore
buffer[scanMethodName] @regex, rangeToSearch, (match, range) ->
rangeToReturn = range
if not rangeToReturn
flags = ""
flags += "i" if @regex.ignoreCase
flags += "g" if @regex.global
flags += "m" if @regex.multiline
@errorMessage = "Pattern not found /#{@regex.source}/#{flags}"
rangeToReturn or range
isRelative: -> true
reverse: ->
new RegexAddress(@regex, !@isReversed)

View File

@@ -0,0 +1,24 @@
Command = require 'command-panel/src/commands/command'
Operation = require 'command-panel/src/operation'
$ = require 'jquery'
module.exports =
class SelectAllMatchesInProject extends Command
regex: null
previewOperations: true
constructor: (pattern) ->
@regex = new RegExp(pattern, 'g')
compile: (project, buffer, range) ->
deferred = $.Deferred()
operations = []
promise = project.scan @regex, ({path, range}) ->
operations.push(new Operation(
project: project
buffer: project.bufferForPath(path)
bufferRange: range
))
promise.done -> deferred.resolve(operations)
deferred.promise()

View File

@@ -0,0 +1,23 @@
Command = require 'command-panel/src/commands/command'
Operation = require 'command-panel/src/operation'
$ = require 'jquery'
module.exports =
class SelectAllMatches extends Command
regex: null
constructor: (pattern) ->
@regex = new RegExp(pattern, 'g')
compile: (project, buffer, ranges) ->
deferred = $.Deferred()
operations = []
for range in ranges
buffer.scanInRange @regex, range, (match, matchRange) ->
operations.push(new Operation(
project: project
buffer: buffer
bufferRange: matchRange
))
deferred.resolve(operations)
deferred.promise()

View File

@@ -0,0 +1,28 @@
Command = require 'command-panel/src/commands/command'
Operation = require 'command-panel/src/operation'
$ = require 'jquery'
module.exports =
class Substitution extends Command
regex: null
replacementText: null
preserveSelections: true
constructor: (pattern, replacementText, options) ->
@replacementText = replacementText
@regex = new RegExp(pattern, options.join(''))
compile: (project, buffer, ranges) ->
deferred = $.Deferred()
operations = []
for range in ranges
buffer.scanInRange @regex, range, (match, matchRange) =>
operations.push(new Operation(
project: project
buffer: buffer
bufferRange: matchRange
newText: @replacementText
preserveSelection: true
))
deferred.resolve(operations)
deferred.promise()

View File

@@ -0,0 +1,9 @@
Address = require 'command-panel/src/commands/address'
Range = require 'range'
module.exports =
class ZeroAddress extends Address
getRange: ->
new Range([0, 0], [0, 0])
isRelative: -> false

View File

@@ -0,0 +1,14 @@
window.keymap.bindKeys '*'
'meta-:': 'command-panel:toggle-preview'
'meta-;': 'command-panel:toggle'
'meta-F': 'command-panel:find-in-project'
window.keymap.bindKeys '.command-panel .preview-list, .command-panel .editor input',
'enter': 'core:confirm'
window.keymap.bindKeys '.editor',
'meta-g': 'command-panel:repeat-relative-address'
'meta-G': 'command-panel:repeat-relative-address-in-reverse'
'meta-e': 'command-panel:set-selection-as-regex-address'
'meta-f': 'command-panel:find-in-file'
'meta-F': 'command-panel:find-in-project'

View File

@@ -0,0 +1,30 @@
{$$$} = require 'space-pen'
module.exports =
class Operation
constructor: ({@project, @buffer, bufferRange, @newText, @preserveSelection, @errorMessage}) ->
@buffer.retain()
@anchorRange = @buffer.addAnchorRange(bufferRange)
getPath: ->
@project.relativize(@buffer.getPath())
getBufferRange: ->
@anchorRange.getBufferRange()
execute: (editSession) ->
@buffer.change(@getBufferRange(), @newText) if @newText
@getBufferRange() unless @preserveSelection
preview: ->
range = @anchorRange.getBufferRange()
line = @buffer.lineForRow(range.start.row)
prefix = line[0...range.start.column]
match = line[range.start.column...range.end.column]
suffix = line[range.end.column..]
{prefix, suffix, match}
destroy: ->
@buffer.release()
@anchorRange.destroy()

View File

@@ -0,0 +1,85 @@
$ = require 'jquery'
{$$$} = require 'space-pen'
ScrollView = require 'scroll-view'
module.exports =
class PreviewList extends ScrollView
@content: ->
@ol class: 'preview-list', tabindex: -1, ->
selectedOperationIndex: 0
operations: null
initialize: (@rootView) ->
super
@on 'core:move-down', => @selectNextOperation(); false
@on 'core:move-up', => @selectPreviousOperation(); false
@on 'core:confirm', => @executeSelectedOperation()
@on 'mousedown', 'li', (e) =>
@setSelectedOperationIndex(parseInt($(e.target).closest('li').data('index')))
@executeSelectedOperation()
destroy: ->
@destroyOperations() if @operations
hasOperations: -> @operations?
populate: (operations) ->
@destroyOperations() if @operations
@operations = operations
@empty()
@html $$$ ->
for operation, index in operations
{prefix, suffix, match} = operation.preview()
@li 'data-index': index, =>
@span operation.getPath(), outlet: "path", class: "path"
@span outlet: "preview", class: "preview", =>
@span prefix
@span match, class: 'match'
@span suffix
@setSelectedOperationIndex(0)
@show()
selectNextOperation: ->
@setSelectedOperationIndex(@selectedOperationIndex + 1)
selectPreviousOperation: ->
@setSelectedOperationIndex(@selectedOperationIndex - 1)
setSelectedOperationIndex: (index) ->
index = Math.max(0, index)
index = Math.min(@operations.length - 1, index)
@children(".selected").removeClass('selected')
element = @children("li:eq(#{index})")
element.addClass('selected')
@scrollToElement(element)
@selectedOperationIndex = index
executeSelectedOperation: ->
operation = @getSelectedOperation()
editSession = @rootView.open(operation.getPath())
bufferRange = operation.execute(editSession)
editSession.setSelectedBufferRange(bufferRange, autoscroll: true) if bufferRange
@rootView.focus()
false
getOperations: ->
new Array(@operations...)
destroyOperations: ->
operation.destroy() for operation in @getOperations()
@operations = null
getSelectedOperation: ->
@operations[@selectedOperationIndex]
scrollToElement: (element) ->
top = @scrollTop() + element.position().top
bottom = top + element.outerHeight()
if bottom > @scrollBottom()
@scrollBottom(bottom)
if top < @scrollTop()
@scrollTop(top)