Merge pull request #681 from github/scope-ranges

Add ability to query for a range covering a given scope/position
This commit is contained in:
Nathan Sobo
2013-08-07 12:14:00 -07:00
10 changed files with 106 additions and 12 deletions

1
.pairs
View File

@@ -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

View File

@@ -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, "<div class='name'><%= User.find(2).full_name %></div>")
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]]

View File

@@ -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()

View File

@@ -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}.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -195,4 +195,7 @@ _.mixin
newObject[key] = value if value?
newObject
isSubset: (potentialSubset, potentialSuperset) ->
_.every potentialSubset, (element) -> _.include(potentialSuperset, element)
_.isEqual = require 'tantamount'