Merge branch 'dev' of github.com:github/atom into ci-testing

This commit is contained in:
Adam Roben 2013-02-27 16:27:02 -05:00
commit beb000ceaf
29 changed files with 595 additions and 75 deletions

View File

@ -17,4 +17,3 @@ Requirements
1. gh-setup atom
2. cd ~/github/atom && `rake install`

View File

@ -1,6 +1,6 @@
# Getting Started
Welcome to Atom. This documentation is intented to offer a basic introduction
Welcome to Atom. This documentation is intended to offer a basic introduction
of how to get productive with this editor. Then we'll delve into more details
about configuring, theming, and extending Atom.

View File

@ -27,7 +27,8 @@ namespace v8_extensions {
"exists", "read", "write", "absolute", "getAllFilePathsAsync", "traverseTree", "isDirectory",
"isFile", "remove", "writeToPasteboard", "readFromPasteboard", "quit", "watchPath", "unwatchPath",
"getWatchedPaths", "unwatchAllPaths", "makeDirectory", "move", "moveToTrash", "reload", "lastModified",
"md5ForPath", "exec", "getPlatform", "setWindowState", "getWindowState"
"md5ForPath", "exec", "getPlatform", "setWindowState", "getWindowState", "isMisspelled",
"getCorrectionsForMisspelling"
};
CefRefPtr<CefV8Value> nativeObject = CefV8Value::CreateObject(NULL);
@ -521,6 +522,29 @@ namespace v8_extensions {
return true;
}
else if (name == "isMisspelled") {
NSString *word = stringFromCefV8Value(arguments[0]);
NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:word startingAt:0];
retval = CefV8Value::CreateBool(range.length > 0);
return true;
}
else if (name == "getCorrectionsForMisspelling") {
NSString *misspelling = stringFromCefV8Value(arguments[0]);
NSSpellChecker *spellchecker = [NSSpellChecker sharedSpellChecker];
NSString *language = [spellchecker language];
NSRange range;
range.location = 0;
range.length = [misspelling length];
NSArray *guesses = [spellchecker guessesForWordRange:range inString:misspelling language:language inSpellDocumentWithTag:0];
CefRefPtr<CefV8Value> v8Guesses = CefV8Value::CreateArray([guesses count]);
for (int i = 0; i < [guesses count]; i++) {
v8Guesses->SetValue(i, CefV8Value::CreateString([[guesses objectAtIndex:i] UTF8String]));
}
retval = v8Guesses;
return true;
}
return false;
};

View File

@ -741,6 +741,7 @@ describe 'Buffer', ->
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
bufferChanged: false
valid: true
}
observeHandler.reset()
@ -751,6 +752,7 @@ describe 'Buffer', ->
oldHeadPosition: [6, 2]
newHeadPosition: [6, 5]
bufferChanged: true
valid: true
}
it "calls the given callback when the marker's tail position changes", ->
@ -762,6 +764,7 @@ describe 'Buffer', ->
oldTailPosition: [4, 20]
newTailPosition: [6, 2]
bufferChanged: false
valid: true
}
observeHandler.reset()
@ -773,6 +776,7 @@ describe 'Buffer', ->
oldTailPosition: [6, 2]
newTailPosition: [6, 5]
bufferChanged: true
valid: true
}
it "calls the callback when the selection's tail is cleared", ->
@ -784,6 +788,7 @@ describe 'Buffer', ->
oldTailPosition: [4, 20]
newTailPosition: [4, 23]
bufferChanged: false
valid: true
}
it "only calls the callback once when both the marker's head and tail positions change due to the same operation", ->
@ -795,6 +800,7 @@ describe 'Buffer', ->
oldHeadPosition: [4, 23]
newHeadPosition: [4, 26]
bufferChanged: true
valid: true
}
observeHandler.reset()
@ -806,6 +812,31 @@ describe 'Buffer', ->
oldHeadPosition: [4, 26]
newHeadPosition: [1, 1]
bufferChanged: false
valid: true
}
it "calls the callback with the valid flag set to false when the marker is invalidated", ->
buffer.deleteRow(4)
expect(observeHandler.callCount).toBe 1
expect(observeHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
bufferChanged: true
valid: false
}
observeHandler.reset()
buffer.undo()
expect(observeHandler.callCount).toBe 1
expect(observeHandler.argsForCall[0][0]).toEqual {
oldTailPosition: [4, 20]
newTailPosition: [4, 20]
oldHeadPosition: [4, 23]
newHeadPosition: [4, 23]
bufferChanged: true
valid: true
}
it "allows the observation subscription to be cancelled", ->
@ -830,19 +861,20 @@ describe 'Buffer', ->
buffer.undo()
expect(buffer.getMarkerRange(marker)).toBeUndefined()
# even "stayValid" markers get destroyed properly
marker2 = buffer.markRange([[4, 20], [4, 23]], stayValid: true)
# even "invalidationStrategy: never" markers get destroyed properly
marker2 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'never')
buffer.delete([[4, 15], [4, 25]])
buffer.destroyMarker(marker2)
buffer.undo()
expect(buffer.getMarkerRange(marker2)).toBeUndefined()
describe "marker updates due to buffer changes", ->
[marker1, marker2] = []
[marker1, marker2, marker3] = []
beforeEach ->
marker1 = buffer.markRange([[4, 20], [4, 23]])
marker2 = buffer.markRange([[4, 20], [4, 23]], stayValid: true)
marker2 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'never')
marker3 = buffer.markRange([[4, 20], [4, 23]], invalidationStrategy: 'between')
describe "when the buffer changes due to a new operation", ->
describe "when the change precedes the marker range", ->
@ -874,20 +906,37 @@ describe 'Buffer', ->
buffer.insert([4, 20], '...')
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 26]]
describe "when the invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.insert([4, 20], '...')
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
describe "when the change is an insertion at the end of the marker range", ->
it "moves the end point", ->
buffer.insert([4, 23], '...')
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 26]]
describe "when the invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.insert([4, 23], '...')
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
describe "when the change surrounds the marker range", ->
describe "when the marker was created with stayValid: false (the default)", ->
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 25]])
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the marker was created with stayValid: true", ->
describe "when the marker's invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 25]])
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'never'", ->
it "does not invalidate the marker, but sets it to an empty range at the end of the change", ->
buffer.change([[4, 15], [4, 25]], "...")
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 18], [4, 18]]
@ -895,14 +944,21 @@ describe 'Buffer', ->
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 23]]
describe "when the change straddles the start of the marker range", ->
describe "when the marker was created with stayValid: false (the default)", ->
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 22]])
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the marker was created with stayValid: true", ->
describe "when the marker's invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.delete([[4, 15], [4, 22]])
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'never'", ->
it "moves the start of the marker range to the end of the change", ->
buffer.delete([[4, 15], [4, 22]])
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 15], [4, 16]]
@ -910,20 +966,49 @@ describe 'Buffer', ->
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the change straddles the end of the marker range", ->
describe "when the marker was created with stayValid: false (the default)", ->
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
it "invalidates the marker", ->
buffer.delete([[4, 22], [4, 25]])
expect(buffer.getMarkerRange(marker1)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the marker was created with stayValid: true", ->
describe "when the marker's invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.delete([[4, 22], [4, 25]])
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'never'", ->
it "moves the end of the marker range to the start of the change", ->
buffer.delete([[4, 22], [4, 25]])
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 22]]
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the change is between the start and the end of the marker range", ->
describe "when the marker's invalidation strategy is 'contains' (the default)", ->
it "does not invalidate the marker", ->
buffer.insert([4, 21], 'x')
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 24]]
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'between'", ->
it "invalidates the marker", ->
buffer.insert([4, 21], 'x')
expect(buffer.getMarkerRange(marker3)).toBeUndefined()
buffer.undo()
expect(buffer.getMarkerRange(marker3)).toEqual [[4, 20], [4, 23]]
describe "when the marker's invalidation strategy is 'never'", ->
it "moves the end of the marker range to the start of the change", ->
buffer.insert([4, 21], 'x')
expect(buffer.getMarkerRange(marker2)).toEqual [[4, 20], [4, 24]]
buffer.undo()
expect(buffer.getMarkerRange(marker1)).toEqual [[4, 20], [4, 23]]
describe "when the buffer changes due to the undo or redo of a previous operation", ->
it "restores invalidated markers when undoing/redoing in the other direction", ->
buffer.change([[4, 21], [4, 24]], "foo")

View File

@ -661,6 +661,7 @@ describe "DisplayBuffer", ->
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
valid: true
}
observeHandler.reset()
@ -676,6 +677,7 @@ describe "DisplayBuffer", ->
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: true
valid: true
}
observeHandler.reset()
@ -691,6 +693,7 @@ describe "DisplayBuffer", ->
newTailScreenPosition: [8, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
valid: true
}
observeHandler.reset()
@ -706,6 +709,7 @@ describe "DisplayBuffer", ->
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: false
valid: true
}
it "calls the callback whenever the marker tail's position changes in the buffer or on screen", ->
@ -721,6 +725,7 @@ describe "DisplayBuffer", ->
newTailScreenPosition: [8, 20]
newTailBufferPosition: [11, 20]
bufferChanged: false
valid: true
}
observeHandler.reset()
@ -736,6 +741,40 @@ describe "DisplayBuffer", ->
newTailScreenPosition: [8, 23]
newTailBufferPosition: [11, 23]
bufferChanged: true
valid: true
}
it "calls the callback whenever the marker is invalidated or revalidated", ->
buffer.deleteRow(8)
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [5, 10]
oldHeadBufferPosition: [8, 10]
newHeadScreenPosition: [5, 10]
newHeadBufferPosition: [8, 10]
oldTailScreenPosition: [5, 4]
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: true
valid: false
}
observeHandler.reset()
buffer.undo()
expect(observeHandler).toHaveBeenCalled()
expect(observeHandler.argsForCall[0][0]).toEqual {
oldHeadScreenPosition: [5, 10]
oldHeadBufferPosition: [8, 10]
newHeadScreenPosition: [5, 10]
newHeadBufferPosition: [8, 10]
oldTailScreenPosition: [5, 4]
oldTailBufferPosition: [8, 4]
newTailScreenPosition: [5, 4]
newTailBufferPosition: [8, 4]
bufferChanged: true
valid: true
}
it "does not call the callback for screen changes that don't change the position of the marker", ->

View File

@ -3,13 +3,13 @@ OnigRegExp = require 'onig-reg-exp'
describe "OnigRegExp", ->
describe ".search(string, index)", ->
it "returns an array of the match and all capture groups", ->
regex = new OnigRegExp("\\w(\\d+)")
regex = OnigRegExp.create("\\w(\\d+)")
result = regex.search("----a123----")
expect(result).toEqual ["a123", "123"]
expect(result.index).toBe 4
expect(result.indices).toEqual [4, 5]
it "returns null if it does not match", ->
regex = new OnigRegExp("\\w(\\d+)")
regex = OnigRegExp.create("\\w(\\d+)")
result = regex.search("--------")
expect(result).toBeNull()

View File

@ -107,5 +107,4 @@ class BufferChangeOperation
if validMarker = @buffer.validMarkers[id]
validMarker.setRange(previousRange)
else if invalidMarker = @buffer.invalidMarkers[id]
@buffer.validMarkers[id] = invalidMarker
invalidMarker.revalidate()

View File

@ -8,9 +8,10 @@ class BufferMarker
headPosition: null
tailPosition: null
suppressObserverNotification: false
stayValid: false
invalidationStrategy: null
constructor: ({@id, @buffer, range, @stayValid, noTail, reverse}) ->
constructor: ({@id, @buffer, range, @invalidationStrategy, noTail, reverse}) ->
@invalidationStrategy ?= 'contains'
@setRange(range, {noTail, reverse})
setRange: (range, options={}) ->
@ -71,23 +72,30 @@ class BufferMarker
newTailPosition = @getTailPosition()
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
tryToInvalidate: (oldRange) ->
containsStart = oldRange.containsPoint(@getStartPosition(), exclusive: true)
containsEnd = oldRange.containsPoint(@getEndPosition(), exclusive: true)
return unless containsEnd or containsStart
tryToInvalidate: (changedRange) ->
betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false)
containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true)
containsEnd = changedRange.containsPoint(@getEndPosition(), exclusive: true)
if @stayValid
previousRange = @getRange()
if containsStart and containsEnd
@setRange([oldRange.end, oldRange.end])
else if containsStart
@setRange([oldRange.end, @getEndPosition()])
else
@setRange([@getStartPosition(), oldRange.start])
[@id, previousRange]
else
@invalidate()
[@id]
switch @invalidationStrategy
when 'between'
if betweenStartAndEnd or containsStart or containsEnd
@invalidate()
[@id]
when 'contains'
if containsStart or containsEnd
@invalidate()
[@id]
when 'never'
if containsStart or containsEnd
previousRange = @getRange()
if containsStart and containsEnd
@setRange([changedRange.end, changedRange.end])
else if containsStart
@setRange([changedRange.end, @getEndPosition()])
else
@setRange([@getStartPosition(), changedRange.start])
[@id, previousRange]
handleBufferChange: (bufferChange) ->
@consolidateObserverNotifications true, =>
@ -113,23 +121,31 @@ class BufferMarker
[newRow, newColumn]
observe: (callback) ->
@on 'position-changed', callback
@on 'changed', callback
cancel: => @unobserve(callback)
unobserve: (callback) ->
@off 'position-changed', callback
@off 'changed', callback
containsPoint: (point) ->
@getRange().containsPoint(point)
notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}) ->
notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged} = {}) ->
return if @suppressObserverNotification
return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition)
if newHeadPosition? and newTailPosition?
return if _.isEqual(newHeadPosition, oldHeadPosition) and _.isEqual(newTailPosition, oldTailPosition)
else if newHeadPosition?
return if _.isEqual(newHeadPosition, oldHeadPosition)
else if newTailPosition?
return if _.isEqual(newTailPosition, oldTailPosition)
oldHeadPosition ?= @getHeadPosition()
newHeadPosition ?= @getHeadPosition()
oldTailPosition ?= @getTailPosition()
newTailPosition ?= @getTailPosition()
@trigger 'position-changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}
valid = @buffer.validMarkers[@id]?
@trigger 'changed', {oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}
consolidateObserverNotifications: (bufferChanged, fn) ->
@suppressObserverNotification = true
@ -141,8 +157,14 @@ class BufferMarker
@suppressObserverNotification = false
@notifyObservers({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged})
invalidate: (preserve) ->
invalidate: ->
delete @buffer.validMarkers[@id]
@buffer.invalidMarkers[@id] = this
@notifyObservers(bufferChanged: true)
revalidate: ->
delete @buffer.invalidMarkers[@id]
@buffer.validMarkers[@id] = this
@notifyObservers(bufferChanged: true)
_.extend BufferMarker.prototype, EventEmitter

View File

@ -7,6 +7,7 @@ class DisplayBufferMarker
bufferMarkerSubscription: null
headScreenPosition: null
tailScreenPosition: null
valid: true
constructor: ({@id, @displayBuffer}) ->
@buffer = @displayBuffer.buffer
@ -57,11 +58,11 @@ class DisplayBufferMarker
observe: (callback) ->
@observeBufferMarkerIfNeeded()
@on 'position-changed', callback
@on 'changed', callback
cancel: => @unobserve(callback)
unobserve: (callback) ->
@off 'position-changed', callback
@off 'changed', callback
@unobserveBufferMarkerIfNeeded()
observeBufferMarkerIfNeeded: ->
@ -69,13 +70,14 @@ class DisplayBufferMarker
@getHeadScreenPosition() # memoize current value
@getTailScreenPosition() # memoize current value
@bufferMarkerSubscription =
@buffer.observeMarker @id, ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged}) =>
@buffer.observeMarker @id, ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged, valid}) =>
@notifyObservers
oldHeadBufferPosition: oldHeadPosition
newHeadBufferPosition: newHeadPosition
oldTailBufferPosition: oldTailPosition
newTailBufferPosition: newTailPosition
bufferChanged: bufferChanged
valid: valid
@displayBuffer.markers[@id] = this
unobserveBufferMarkerIfNeeded: ->
@ -83,28 +85,37 @@ class DisplayBufferMarker
@bufferMarkerSubscription.cancel()
delete @displayBuffer.markers[@id]
notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged}) ->
notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged, valid} = {}) ->
oldHeadScreenPosition = @getHeadScreenPosition()
@headScreenPosition = null
newHeadScreenPosition = @getHeadScreenPosition()
newHeadScreenPosition = oldHeadScreenPosition
oldTailScreenPosition = @getTailScreenPosition()
@tailScreenPosition = null
newTailScreenPosition = @getTailScreenPosition()
newTailScreenPosition = oldTailScreenPosition
valid ?= true
return if _.isEqual(newHeadScreenPosition, oldHeadScreenPosition) and _.isEqual(newTailScreenPosition, oldTailScreenPosition)
if valid
@headScreenPosition = null
newHeadScreenPosition = @getHeadScreenPosition()
@tailScreenPosition = null
newTailScreenPosition = @getTailScreenPosition()
validChanged = valid isnt @valid
headScreenPositionChanged = not _.isEqual(newHeadScreenPosition, oldHeadScreenPosition)
tailScreenPositionChanged = not _.isEqual(newTailScreenPosition, oldTailScreenPosition)
return unless validChanged or headScreenPositionChanged or tailScreenPositionChanged
oldHeadBufferPosition ?= @getHeadBufferPosition()
newHeadBufferPosition = @getHeadBufferPosition()
newHeadBufferPosition = @getHeadBufferPosition() ? oldHeadBufferPosition
oldTailBufferPosition ?= @getTailBufferPosition()
newTailBufferPosition = @getTailBufferPosition()
newTailBufferPosition = @getTailBufferPosition() ? oldTailBufferPosition
@valid = valid
@trigger 'position-changed', {
@trigger 'changed', {
oldHeadScreenPosition, newHeadScreenPosition,
oldTailScreenPosition, newTailScreenPosition,
oldHeadBufferPosition, newHeadBufferPosition,
oldTailBufferPosition, newTailBufferPosition,
bufferChanged
valid
}
_.extend DisplayBufferMarker.prototype, EventEmitter

View File

@ -332,8 +332,9 @@ class DisplayBuffer
getMarkers: ->
_.values(@markers)
markScreenRange: (screenRange) ->
@markBufferRange(@bufferRangeForScreenRange(screenRange))
markScreenRange: (args...) ->
bufferRange = @bufferRangeForScreenRange(args.shift())
@markBufferRange(bufferRange, args...)
markBufferRange: (args...) ->
@buffer.markRange(args...)

View File

@ -538,11 +538,11 @@ class EditSession
_.last(@cursors)
addCursorAtScreenPosition: (screenPosition) ->
marker = @markScreenPosition(screenPosition, stayValid: true)
marker = @markScreenPosition(screenPosition, invalidationStrategy: 'never')
@addSelection(marker).cursor
addCursorAtBufferPosition: (bufferPosition) ->
marker = @markBufferPosition(bufferPosition, stayValid: true)
marker = @markBufferPosition(bufferPosition, invalidationStrategy: 'never')
@addSelection(marker).cursor
addCursor: (marker) ->
@ -565,7 +565,7 @@ class EditSession
selection
addSelectionForBufferRange: (bufferRange, options={}) ->
options = _.defaults({stayValid: true}, options)
options = _.defaults({invalidationStrategy: 'never'}, options)
marker = @markBufferRange(bufferRange, options)
@addSelection(marker)

View File

@ -24,7 +24,7 @@ class Git
ignore: 1 << 14
constructor: (path, options={}) ->
@repo = new GitRepository(path)
@repo = GitRepository.open(path)
refreshIndexOnFocus = options.refreshIndexOnFocus ? true
if refreshIndexOnFocus
$ = require 'jquery'

View File

@ -30,13 +30,13 @@ class LanguageMode
buffer = @editSession.buffer
commentStartRegexString = _.escapeRegExp(commentStartString).replace(/(\s+)$/, '($1)?')
commentStartRegex = new OnigRegExp("^(\\s*)(#{commentStartRegexString})")
commentStartRegex = OnigRegExp.create("^(\\s*)(#{commentStartRegexString})")
shouldUncomment = commentStartRegex.test(buffer.lineForRow(start))
if commentEndString = syntax.getProperty(scopes, "editor.commentEnd")
if shouldUncomment
commentEndRegexString = _.escapeRegExp(commentEndString).replace(/^(\s+)/, '($1)?')
commentEndRegex = new OnigRegExp("(#{commentEndRegexString})(\\s*)$")
commentEndRegex = OnigRegExp.create("(#{commentEndRegexString})(\\s*)$")
startMatch = commentStartRegex.search(buffer.lineForRow(start))
endMatch = commentEndRegex.search(buffer.lineForRow(end))
if startMatch and endMatch
@ -152,12 +152,12 @@ class LanguageMode
increaseIndentRegexForScopes: (scopes) ->
if increaseIndentPattern = syntax.getProperty(scopes, 'editor.increaseIndentPattern')
new OnigRegExp(increaseIndentPattern)
OnigRegExp.create(increaseIndentPattern)
decreaseIndentRegexForScopes: (scopes) ->
if decreaseIndentPattern = syntax.getProperty(scopes, 'editor.decreaseIndentPattern')
new OnigRegExp(decreaseIndentPattern)
OnigRegExp.create(decreaseIndentPattern)
foldEndRegexForScopes: (scopes) ->
if foldEndPattern = syntax.getProperty(scopes, 'editor.foldEndPattern')
new OnigRegExp(foldEndPattern)
OnigRegExp.create(foldEndPattern)

View File

@ -57,7 +57,11 @@ class Range
else
otherRange.intersectsWith(this)
containsPoint: (point, { exclusive } = {}) ->
containsRange: (otherRange, {exclusive} = {}) ->
{ start, end } = Range.fromObject(otherRange)
@containsPoint(start, {exclusive}) and @containsPoint(end, {exclusive})
containsPoint: (point, {exclusive} = {}) ->
point = Point.fromObject(point)
if exclusive
point.isGreaterThan(@start) and point.isLessThan(@end)

View File

@ -28,7 +28,7 @@ class TextMateGrammar
constructor: ({ @name, @fileTypes, @scopeName, patterns, repository, @foldingStopMarker, firstLineMatch}) ->
@initialRule = new Rule(this, {@scopeName, patterns})
@repository = {}
@firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch
@firstLineRegex = OnigRegExp.create(firstLineMatch) if firstLineMatch
@fileTypes ?= []
for name, data of repository
@ -111,7 +111,7 @@ class Rule
regex = pattern.regexSource
regexes.push regex if regex
regexScanner = new OnigScanner(regexes)
regexScanner = OnigScanner.create(regexes)
regexScanner.patterns = patterns
@scannersByBaseGrammarName[baseGrammar.name] = regexScanner unless anchored
regexScanner

View File

@ -7,4 +7,3 @@ module.exports =
rootView.eachEditor (editor) =>
if editor.attached and not editor.mini
@autoCompleteViews.push new AutocompleteView(editor)

View File

@ -0,0 +1,2 @@
'.editor':
'meta-0': 'editor:correct-misspelling'

View File

@ -0,0 +1,71 @@
{$$} = require 'space-pen'
Range = require 'range'
SelectList = require 'select-list'
module.exports =
class CorrectionsView extends SelectList
@viewClass: -> "corrections #{super} popover-list"
editor: null
corrections: null
misspellingRange: null
aboveCursor: false
initialize: (@editor, @corrections, @misspellingRange) ->
super
@attach()
itemForElement: (word) ->
$$ ->
@li word
selectNextItem: ->
super
false
selectPreviousItem: ->
super
false
confirmed: (correction) ->
@cancel()
return unless correction
@editor.transact =>
@editor.setSelectedBufferRange(@editor.bufferRangeForScreenRange(@misspellingRange))
@editor.insertText(correction)
attach: ->
@aboveCursor = false
if @corrections.length > 0
@setArray(@corrections)
else
@setError("No corrections found")
@editor.appendToLinesView(this)
@setPosition()
@miniEditor.focus()
detach: ->
super
@editor.focus()
setPosition: ->
{ left, top } = @editor.pixelPositionForScreenPosition(@misspellingRange.start)
height = @outerHeight()
potentialTop = top + @editor.lineHeight
potentialBottom = potentialTop - @editor.scrollTop() + height
if @aboveCursor or potentialBottom > @editor.outerHeight()
@aboveCursor = true
@css(left: left, top: top - height, bottom: 'inherit')
else
@css(left: left, top: potentialTop, bottom: 'inherit')
populateList: ->
super
@setPosition()

View File

@ -0,0 +1,61 @@
{View} = require 'space-pen'
Range = require 'range'
CorrectionsView = require './corrections-view'
module.exports =
class MisspellingView extends View
@content: ->
@div class: 'misspelling'
initialize: (range, @editor) ->
@editSession = @editor.activeEditSession
range = @editSession.screenRangeForBufferRange(Range.fromObject(range))
@startPosition = range.start
@endPosition = range.end
@misspellingValid = true
@marker = @editSession.markScreenRange(range, invalidationStrategy: 'between')
@editSession.observeMarker @marker, ({newHeadScreenPosition, newTailScreenPosition, valid}) =>
@startPosition = newTailScreenPosition
@endPosition = newHeadScreenPosition
@updateDisplayPosition = valid
@misspellingValid = valid
@hide() unless valid
@subscribe @editor, 'editor:display-updated', =>
@updatePosition() if @updateDisplayPosition
@editor.command 'editor:correct-misspelling', =>
return unless @misspellingValid and @containsCursor()
screenRange = @getScreenRange()
misspelling = @editor.getTextInRange(@editor.bufferRangeForScreenRange(screenRange))
corrections = $native.getCorrectionsForMisspelling(misspelling)
@correctionsView?.remove()
@correctionsView = new CorrectionsView(@editor, corrections, screenRange)
@updatePosition()
getScreenRange: ->
new Range(@startPosition, @endPosition)
containsCursor: ->
cursor = @editor.getCursorScreenPosition()
@getScreenRange().containsPoint(cursor, exclusive: false)
updatePosition: ->
@updateDisplayPosition = false
startPixelPosition = @editor.pixelPositionForScreenPosition(@startPosition)
endPixelPosition = @editor.pixelPositionForScreenPosition(@endPosition)
@css
top: startPixelPosition.top
left: startPixelPosition.left
width: endPixelPosition.left - startPixelPosition.left
height: @editor.lineHeight
@show()
destroy: ->
@misspellingValid = false
@editSession.destroyMarker(@marker)
@correctionsView?.remove()
@remove()

View File

@ -0,0 +1,14 @@
module.exports =
findMisspellings: (text) ->
wordRegex = /(?:^|[\s\[\]])([a-zA-Z']+)(?=[\s\.\[\]]|$)/g
row = 0
misspellings = []
for line in text.split('\n')
while matches = wordRegex.exec(line)
word = matches[1]
continue unless $native.isMisspelled(word)
startColumn = matches.index + matches[0].length - word.length
endColumn = startColumn + word.length
misspellings.push([[row, startColumn], [row, endColumn]])
row++
callTaskMethod('misspellingsFound', misspellings)

View File

@ -0,0 +1,13 @@
Task = require 'task'
module.exports =
class SpellCheckTask extends Task
constructor: (@text, @callback) ->
super('spell-check/lib/spell-check-handler')
started: ->
@callWorkerMethod('findMisspellings', @text)
misspellingsFound: (misspellings) ->
@done()
@callback(misspellings)

View File

@ -0,0 +1,53 @@
{View} = require 'space-pen'
_ = require 'underscore'
SpellCheckTask = require './spell-check-task'
MisspellingView = require './misspelling-view'
module.exports =
class SpellCheckView extends View
@content: ->
@div class: 'spell-check'
views: []
initialize: (@editor) ->
@subscribe @editor, 'editor:path-changed', => @subscribeToBuffer()
@subscribe @editor, 'editor:grammar-changed', => @subscribeToBuffer()
@observeConfig 'editor.fontSize', => @subscribeToBuffer()
@observeConfig 'spell-check.grammars', => @subscribeToBuffer()
@subscribeToBuffer()
subscribeToBuffer: ->
@destroyViews()
@task?.abort()
return unless @spellCheckCurrentGrammar()
@buffer?.off '.spell-check'
@buffer = @editor.getBuffer()
@buffer.on 'contents-modified.spell-check', => @updateMisspellings()
@updateMisspellings()
spellCheckCurrentGrammar: ->
grammar = @editor.getGrammar().scopeName
_.contains config.get('spell-check.grammars'), grammar
destroyViews: ->
if @views
view.destroy() for view in @views
@views = []
addViews: (misspellings) ->
for misspelling in misspellings
view = new MisspellingView(misspelling, @editor)
@views.push(view)
@append(view)
updateMisspellings: ->
@task?.abort()
callback = (misspellings) =>
@destroyViews()
@addViews(misspellings)
@task = new SpellCheckTask(@buffer.getText(), callback)
@task.start()

View File

@ -0,0 +1,20 @@
SpellCheckView = require './spell-check-view'
module.exports =
configDefaults:
grammars: [
'text.plain'
'source.gfm'
'text.git-commit'
]
activate: ->
if syntax.grammars.length > 1
@subscribeToEditors()
else
syntax.on 'grammars-loaded', => @subscribeToEditors()
subscribeToEditors: ->
rootView.eachEditor (editor) ->
if editor.attached and not editor.mini
editor.underlayer.append(new SpellCheckView(editor))

View File

@ -0,0 +1 @@
'main': 'lib/spell-check.coffee'

View File

@ -0,0 +1,98 @@
RootView = require 'root-view'
describe "Spell check", ->
[editor] = []
beforeEach ->
window.rootView = new RootView
rootView.open('sample.js')
config.set('spell-check.grammars', [])
window.loadPackage('spell-check')
rootView.attachToDom()
editor = rootView.getActiveEditor()
it "decorates all misspelled words", ->
editor.setText("This middle of thiss sentencts has issues.")
config.set('spell-check.grammars', ['source.js'])
waitsFor ->
editor.find('.misspelling').length > 0
runs ->
expect(editor.find('.misspelling').length).toBe 2
typo1StartPosition = editor.pixelPositionForBufferPosition([0, 15])
typo1EndPosition = editor.pixelPositionForBufferPosition([0, 20])
expect(editor.find('.misspelling:eq(0)').position()).toEqual typo1StartPosition
expect(editor.find('.misspelling:eq(0)').width()).toBe typo1EndPosition.left - typo1StartPosition.left
typo2StartPosition = editor.pixelPositionForBufferPosition([0, 21])
typo2EndPosition = editor.pixelPositionForBufferPosition([0, 30])
expect(editor.find('.misspelling:eq(1)').position()).toEqual typo2StartPosition
expect(editor.find('.misspelling:eq(1)').width()).toBe typo2EndPosition.left - typo2StartPosition.left
it "hides decorations when a misspelled word is edited", ->
editor.setText('notaword')
advanceClock(editor.getBuffer().stoppedChangingDelay)
config.set('spell-check.grammars', ['source.js'])
waitsFor ->
editor.find('.misspelling').length > 0
runs ->
expect(editor.find('.misspelling').length).toBe 1
editor.moveCursorToEndOfLine()
editor.insertText('a')
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editor.find('.misspelling')).toBeHidden()
describe "when spell checking for a grammar is removed", ->
it "removes all current decorations", ->
editor.setText('notaword')
advanceClock(editor.getBuffer().stoppedChangingDelay)
config.set('spell-check.grammars', ['source.js'])
waitsFor ->
editor.find('.misspelling').length > 0
runs ->
expect(editor.find('.misspelling').length).toBe 1
config.set('spell-check.grammars', [])
expect(editor.find('.misspelling').length).toBe 0
describe "when 'editor:correct-misspelling' is triggered on the editor", ->
describe "when the cursor touches a misspelling that has corrections", ->
it "displays the corrections for the misspelling and replaces the misspelling when a correction is selected", ->
editor.setText('fryday')
advanceClock(editor.getBuffer().stoppedChangingDelay)
config.set('spell-check.grammars', ['source.js'])
waitsFor ->
editor.find('.misspelling').length > 0
runs ->
editor.trigger 'editor:correct-misspelling'
expect(editor.find('.corrections').length).toBe 1
expect(editor.find('.corrections li').length).toBeGreaterThan 0
expect(editor.find('.corrections li:first').text()).toBe "Friday"
editor.find('.corrections').view().confirmSelection()
expect(editor.getText()).toBe 'Friday'
expect(editor.getCursorBufferPosition()).toEqual [0, 6]
advanceClock(editor.getBuffer().stoppedChangingDelay)
expect(editor.find('.misspelling')).toBeHidden()
expect(editor.find('.corrections').length).toBe 0
describe "when the cursor touches a misspelling that has no corrections", ->
it "displays a message saying no corrections found", ->
editor.setText('asdfasdf')
advanceClock(editor.getBuffer().stoppedChangingDelay)
config.set('spell-check.grammars', ['source.js'])
waitsFor ->
editor.find('.misspelling').length > 0
runs ->
editor.trigger 'editor:correct-misspelling'
expect(editor.find('.corrections').length).toBe 1
expect(editor.find('.corrections li').length).toBe 0
expect(editor.find('.corrections .error').text()).toBe "No corrections found"

View File

@ -0,0 +1,4 @@
.misspelling {
border-bottom: 1px dashed rgba(250, 128, 114, .5);
position: absolute;
}

View File

@ -1,11 +1,11 @@
module.exports =
class GitRepository
constructor: (path) ->
@open: (path) ->
unless repo = $git.getRepository(path)
throw new Error("No Git repository found searching path: #{path}")
repo.constructor = GitRepository
repo.__proto__ = GitRepository.prototype
return repo
repo
getHead: $git.getHead
getPath: $git.getPath

View File

@ -1,11 +1,11 @@
module.exports =
class OnigRegExp
constructor: (source) ->
@create: (source) ->
regexp = $onigRegExp.buildOnigRegExp(source);
regexp.constructor = OnigRegExp
regexp.__proto__ = OnigRegExp.prototype
regexp.source = source
return regexp
regexp
search: $onigRegExp.search
test: $onigRegExp.test

View File

@ -1,10 +1,10 @@
module.exports =
class OnigScanner
constructor: (sources) ->
@create: (sources) ->
scanner = $onigScanner.buildScanner(sources)
scanner.constructor = OnigScanner
scanner.__proto__ = OnigScanner.prototype
scanner.sources = sources
return scanner
scanner
findNextMatch: $onigScanner.findNextMatch