diff --git a/.pairs b/.pairs
index d3383a8a4..1d1156f8a 100644
--- a/.pairs
+++ b/.pairs
@@ -8,6 +8,7 @@ pairs:
jp: Justin Palmer; justin
gt: Garen Torikian; garen
mc: Matt Colyer; mcolyer
+ jr: Jason Rudolph; jasonrudolph
email:
domain: github.com
#global: true
diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee
index 2a1ce0c0f..8648931e1 100644
--- a/spec/app/tokenized-buffer-spec.coffee
+++ b/spec/app/tokenized-buffer-spec.coffee
@@ -12,7 +12,11 @@ describe "TokenizedBuffer", ->
TokenizedBuffer.prototype.chunkSize = 5
jasmine.unspy(TokenizedBuffer.prototype, 'tokenizeInBackground')
+ startTokenizing = (tokenizedBuffer) ->
+ tokenizedBuffer.setVisible(true)
+
fullyTokenize = (tokenizedBuffer) ->
+ tokenizedBuffer.setVisible(true)
advanceClock() while tokenizedBuffer.firstInvalidRow()?
changeHandler?.reset()
@@ -20,7 +24,7 @@ describe "TokenizedBuffer", ->
beforeEach ->
buffer = project.bufferForPath('sample.js')
tokenizedBuffer = new TokenizedBuffer(buffer)
- tokenizedBuffer.setVisible(true)
+ startTokenizing(tokenizedBuffer)
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
afterEach ->
@@ -300,7 +304,7 @@ describe "TokenizedBuffer", ->
atom.activatePackage('coffee-script-tmbundle', sync: true)
buffer = project.bufferForPath('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer(buffer)
- tokenizedBuffer.setVisible(true)
+ startTokenizing(tokenizedBuffer)
afterEach ->
tokenizedBuffer.destroy()
@@ -333,7 +337,6 @@ describe "TokenizedBuffer", ->
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer(buffer)
- tokenizedBuffer.setVisible(true)
fullyTokenize(tokenizedBuffer)
afterEach ->
@@ -371,7 +374,6 @@ describe "TokenizedBuffer", ->
buffer = project.bufferForPath(null, "
<%= User.find(2).full_name %>
")
tokenizedBuffer = new TokenizedBuffer(buffer)
tokenizedBuffer.setGrammar(syntax.selectGrammar('test.erb'))
- tokenizedBuffer.setVisible(true)
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
@@ -390,8 +392,25 @@ describe "TokenizedBuffer", ->
it "returns the correct token (regression)", ->
buffer = project.bufferForPath('sample.js')
tokenizedBuffer = new TokenizedBuffer(buffer)
- tokenizedBuffer.setVisible(true)
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([1,0]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1,1]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1,2]).scopes).toEqual ["source.js", "storage.modifier.js"]
+
+ describe ".bufferRangeForScopeAtPosition(selector, position)", ->
+ beforeEach ->
+ buffer = project.bufferForPath('sample.js')
+ tokenizedBuffer = new TokenizedBuffer(buffer)
+ fullyTokenize(tokenizedBuffer)
+
+ describe "when the selector does not match the token at the position", ->
+ it "returns a falsy value", ->
+ expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.bogus', [0, 1])).toBeFalsy()
+
+ describe "when the selector matches a single token at the position", ->
+ it "returns the range covered by the token", ->
+ expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.storage.modifier.js', [0, 1])).toEqual [[0, 0], [0, 3]]
+
+ describe "when the selector matches a run of multiple tokens at the position", ->
+ it "returns the range covered by all contigous tokens (within a single line)", ->
+ expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.function', [1, 18])).toEqual [[1, 6], [1, 28]]
diff --git a/spec/stdlib/underscore-extensions-spec.coffee b/spec/stdlib/underscore-extensions-spec.coffee
index 7bf4bf90d..f75a720d8 100644
--- a/spec/stdlib/underscore-extensions-spec.coffee
+++ b/spec/stdlib/underscore-extensions-spec.coffee
@@ -107,3 +107,11 @@ describe "underscore extensions", ->
object:
first: 1
second: 2
+
+ describe "_.isSubset(potentialSubset, potentialSuperset)", ->
+ it "returns whether the first argument is a subset of the second", ->
+ expect(_.isSubset([1, 2], [1, 2])).toBeTruthy()
+ expect(_.isSubset([1, 2], [1, 2, 3])).toBeTruthy()
+ expect(_.isSubset([], [1])).toBeTruthy()
+ expect(_.isSubset([], [])).toBeTruthy()
+ expect(_.isSubset([1, 2], [2, 3])).toBeFalsy()
diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee
index 0a1d98da1..00e702036 100644
--- a/src/app/display-buffer.coffee
+++ b/src/app/display-buffer.coffee
@@ -282,6 +282,9 @@ class DisplayBuffer
scopesForBufferPosition: (bufferPosition) ->
@tokenizedBuffer.scopesForPosition(bufferPosition)
+ bufferRangeForScopeAtPosition: (selector, position) ->
+ @tokenizedBuffer.bufferRangeForScopeAtPosition(selector, position)
+
# Retrieves the grammar's token for a buffer position.
#
# bufferPosition - A {Point} in the {Buffer}.
diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee
index a422d5f01..ea4e086d0 100644
--- a/src/app/edit-session.coffee
+++ b/src/app/edit-session.coffee
@@ -388,6 +388,9 @@ class EditSession
# {Delegates to: DisplayBuffer.scopesForBufferPosition}
scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)
+ bufferRangeForScopeAtCursor: (selector) ->
+ @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition())
+
# {Delegates to: DisplayBuffer.tokenForBufferPosition}
tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
@@ -1042,6 +1045,8 @@ class EditSession
getTextInBufferRange: (range) ->
@buffer.getTextInRange(range)
+ setTextInBufferRange: (range, text) -> @getBuffer().change(range, text)
+
# Retrieves the range for the current paragraph.
#
# A paragraph is defined as a block of text surrounded by empty lines.
diff --git a/src/app/range.coffee b/src/app/range.coffee
index 2eb393e98..1b3ec66da 100644
--- a/src/app/range.coffee
+++ b/src/app/range.coffee
@@ -31,9 +31,9 @@ class Range
# columnDelta - A {Number} indicating how far from the starting {Point} the range's column should be
#
# Returns the new {Range}.
- @fromPointWithDelta: (point, rowDelta, columnDelta) ->
- pointA = Point.fromObject(point)
- pointB = new Point(point.row + rowDelta, point.column + columnDelta)
+ @fromPointWithDelta: (pointA, rowDelta, columnDelta) ->
+ pointA = Point.fromObject(pointA)
+ pointB = new Point(pointA.row + rowDelta, pointA.column + columnDelta)
new Range(pointA, pointB)
# Creates a new `Range` object based on two {Point}s.
diff --git a/src/app/token.coffee b/src/app/token.coffee
index 8fae0930f..0d011ffc4 100644
--- a/src/app/token.coffee
+++ b/src/app/token.coffee
@@ -109,6 +109,12 @@ class Token
isOnlyWhitespace: ->
not /\S/.test(@value)
+ matchesScopeSelector: (selector) ->
+ targetClasses = selector.replace(/^\.?/, '').split('.')
+ _.any @scopes, (scope) ->
+ scopeClasses = scope.split('.')
+ _.isSubset(targetClasses, scopeClasses)
+
getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})->
invisibles ?= {}
html = @value
diff --git a/src/app/tokenized-buffer.coffee b/src/app/tokenized-buffer.coffee
index 98f1d2cc5..18affef78 100644
--- a/src/app/tokenized-buffer.coffee
+++ b/src/app/tokenized-buffer.coffee
@@ -200,8 +200,34 @@ class TokenizedBuffer
@tokenForPosition(position).scopes
tokenForPosition: (position) ->
+ {row, column} = Point.fromObject(position)
+ @tokenizedLines[row].tokenAtBufferColumn(column)
+
+ tokenStartPositionForPosition: (position) ->
+ {row, column} = Point.fromObject(position)
+ column = @tokenizedLines[row].tokenStartColumnForBufferColumn(column)
+ new Point(row, column)
+
+ bufferRangeForScopeAtPosition: (selector, position) ->
position = Point.fromObject(position)
- @tokenizedLines[position.row].tokenAtBufferColumn(position.column)
+ tokenizedLine = @tokenizedLines[position.row]
+ startIndex = tokenizedLine.tokenIndexAtBufferColumn(position.column)
+
+ for index in [startIndex..0]
+ token = tokenizedLine.tokenAtIndex(index)
+ break unless token.matchesScopeSelector(selector)
+ firstToken = token
+
+ for index in [startIndex...tokenizedLine.getTokenCount()]
+ token = tokenizedLine.tokenAtIndex(index)
+ break unless token.matchesScopeSelector(selector)
+ lastToken = token
+
+ return unless firstToken? and lastToken?
+
+ startColumn = tokenizedLine.bufferColumnForToken(firstToken)
+ endColumn = tokenizedLine.bufferColumnForToken(lastToken) + lastToken.bufferDelta
+ new Range([position.row, startColumn], [position.row, endColumn])
destroy: ->
@unsubscribe()
diff --git a/src/app/tokenized-line.coffee b/src/app/tokenized-line.coffee
index 9b66606b8..18bbecbcc 100644
--- a/src/app/tokenized-line.coffee
+++ b/src/app/tokenized-line.coffee
@@ -90,11 +90,22 @@ class TokenizedLine
@lineEnding is null
tokenAtBufferColumn: (bufferColumn) ->
+ @tokens[@tokenIndexAtBufferColumn(bufferColumn)]
+
+ tokenIndexAtBufferColumn: (bufferColumn) ->
+ delta = 0
+ for token, index in @tokens
+ delta += token.bufferDelta
+ return index if delta > bufferColumn
+ index - 1
+
+ tokenStartColumnForBufferColumn: (bufferColumn) ->
delta = 0
for token in @tokens
- delta += token.bufferDelta
- return token if delta > bufferColumn
- token
+ nextDelta = delta + token.bufferDelta
+ break if nextDelta > bufferColumn
+ delta = nextDelta
+ delta
breakOutAtomicTokens: (inputTokens, tabLength) ->
outputTokens = []
@@ -112,3 +123,15 @@ class TokenizedLine
return true if _.contains(scope.split('.'), 'comment')
break
false
+
+ tokenAtIndex: (index) ->
+ @tokens[index]
+
+ getTokenCount: ->
+ @tokens.length
+
+ bufferColumnForToken: (targetToken) ->
+ column = 0
+ for token in @tokens
+ return column if token is targetToken
+ column += token.bufferDelta
diff --git a/src/stdlib/underscore-extensions.coffee b/src/stdlib/underscore-extensions.coffee
index 7d203ce76..5ad04b67a 100644
--- a/src/stdlib/underscore-extensions.coffee
+++ b/src/stdlib/underscore-extensions.coffee
@@ -195,4 +195,7 @@ _.mixin
newObject[key] = value if value?
newObject
+ isSubset: (potentialSubset, potentialSuperset) ->
+ _.every potentialSubset, (element) -> _.include(potentialSuperset, element)
+
_.isEqual = require 'tantamount'