mirror of
https://github.com/atom/atom.git
synced 2026-01-22 21:38:10 -05:00
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:
1
.pairs
1
.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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -195,4 +195,7 @@ _.mixin
|
||||
newObject[key] = value if value?
|
||||
newObject
|
||||
|
||||
isSubset: (potentialSubset, potentialSuperset) ->
|
||||
_.every potentialSubset, (element) -> _.include(potentialSuperset, element)
|
||||
|
||||
_.isEqual = require 'tantamount'
|
||||
|
||||
Reference in New Issue
Block a user