From 6cc4cf87b96736fd5a98ac3ff7ca1ec0c267dc01 Mon Sep 17 00:00:00 2001 From: Jason Rudolph & Nathan Sobo Date: Wed, 31 Jul 2013 15:46:39 -0700 Subject: [PATCH 1/7] :lipstick: Add intention-revealing helper method --- spec/app/tokenized-buffer-spec.coffee | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee index 2a1ce0c0f..78af67309 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,7 +392,6 @@ 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"] From eaba8ef016090e8ca7a52c349738dc613d61070a Mon Sep 17 00:00:00 2001 From: Jason Rudolph & Nathan Sobo Date: Wed, 31 Jul 2013 15:47:15 -0700 Subject: [PATCH 2/7] Add Jason Rudolph to .pairs file --- .pairs | 1 + 1 file changed, 1 insertion(+) 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 From 39d15d6087724bcd0c8d87f48edfb945b96bc3d8 Mon Sep 17 00:00:00 2001 From: Jason Rudolph & Nathan Sobo Date: Wed, 31 Jul 2013 16:21:41 -0700 Subject: [PATCH 3/7] Add _.isSubset --- spec/stdlib/underscore-extensions-spec.coffee | 8 ++++++++ src/stdlib/underscore-extensions.coffee | 3 +++ 2 files changed, 11 insertions(+) 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/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' From 5f323cc67ca60150e54b6300c500f87c91e56139 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 6 Aug 2013 17:35:03 -0600 Subject: [PATCH 4/7] Add TokenizedBuffer.bufferRangeForScopeAtPosition(selector, position) You can call this method with a selector and a position and get the range of any matching scope containing the given position, or a falsy value if the scope does not match at that position. --- spec/app/tokenized-buffer-spec.coffee | 18 +++++++++++++++++ src/app/token.coffee | 6 ++++++ src/app/tokenized-buffer.coffee | 28 +++++++++++++++++++++++++- src/app/tokenized-line.coffee | 29 ++++++++++++++++++++++++--- 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/spec/app/tokenized-buffer-spec.coffee b/spec/app/tokenized-buffer-spec.coffee index 78af67309..8648931e1 100644 --- a/spec/app/tokenized-buffer-spec.coffee +++ b/spec/app/tokenized-buffer-spec.coffee @@ -396,3 +396,21 @@ describe "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/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 From 88603e2771a5bbe283f6fc2bf7a3df409278f996 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 6 Aug 2013 17:35:39 -0600 Subject: [PATCH 5/7] Use normalized variables instead of original parameters --- src/app/range.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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. From 19545e1113bcbd727c7a352871ba93c1936d3b77 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 6 Aug 2013 19:00:36 -0600 Subject: [PATCH 6/7] Add setTextInBufferRange to EditSession We should probably rename TextBuffer.change to .setTextInRange as well --- src/app/edit-session.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index a422d5f01..f6575337f 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -1042,6 +1042,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. From ab80da4363191e96d74b2e9202a9827835dbc52e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 6 Aug 2013 19:00:53 -0600 Subject: [PATCH 7/7] Add EditSession.bufferRangeForScopeAtCursor --- src/app/display-buffer.coffee | 3 +++ src/app/edit-session.coffee | 3 +++ 2 files changed, 6 insertions(+) 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 f6575337f..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)