Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Commit

Permalink
Merge pull request #195 from dirk-thomas/match_with_context
Browse files Browse the repository at this point in the history
add context lines to matches
  • Loading branch information
Max Brunsfeld authored Mar 8, 2017
2 parents 399ecda + 1430488 commit a594c2b
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 23 deletions.
33 changes: 33 additions & 0 deletions spec/text-buffer-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2046,11 +2046,40 @@ describe "TextBuffer", ->
expect(matches[0].range).toEqual [[3, 31], [3, 38]]
expect(matches[0].lineText).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(matches[0].lineTextOffset).toBe 0
expect(matches[0].leadingContextLines.length).toBe 0
expect(matches[0].trailingContextLines.length).toBe 0

expect(matches[1].matchText).toBe 'current'
expect(matches[1].range).toEqual [[5, 6], [5, 13]]
expect(matches[1].lineText).toBe ' current = items.shift();'
expect(matches[1].lineTextOffset).toBe 0
expect(matches[1].leadingContextLines.length).toBe 0
expect(matches[1].trailingContextLines.length).toBe 0

it "calls the given function with the information about each match including context lines", ->
matches = []
buffer.scan /current/g, {leadingContextLineCount: 1, trailingContextLineCount: 2}, (match) -> matches.push(match)
expect(matches.length).toBe 5

expect(matches[0].matchText).toBe 'current'
expect(matches[0].range).toEqual [[3, 31], [3, 38]]
expect(matches[0].lineText).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(matches[0].lineTextOffset).toBe 0
expect(matches[0].leadingContextLines.length).toBe 1
expect(matches[0].leadingContextLines[0]).toBe ' if (items.length <= 1) return items;'
expect(matches[0].trailingContextLines.length).toBe 2
expect(matches[0].trailingContextLines[0]).toBe ' while(items.length > 0) {'
expect(matches[0].trailingContextLines[1]).toBe ' current = items.shift();'

expect(matches[1].matchText).toBe 'current'
expect(matches[1].range).toEqual [[5, 6], [5, 13]]
expect(matches[1].lineText).toBe ' current = items.shift();'
expect(matches[1].lineTextOffset).toBe 0
expect(matches[1].leadingContextLines.length).toBe 1
expect(matches[1].leadingContextLines[0]).toBe ' while(items.length > 0) {'
expect(matches[1].trailingContextLines.length).toBe 2
expect(matches[1].trailingContextLines[0]).toBe ' current < pivot ? left.push(current) : right.push(current);'
expect(matches[1].trailingContextLines[1]).toBe ' }'

describe "::backwardsScan(regex, fn)", ->
beforeEach ->
Expand All @@ -2066,11 +2095,15 @@ describe "TextBuffer", ->
expect(matches[0].range).toEqual [[6, 56], [6, 63]]
expect(matches[0].lineText).toBe ' current < pivot ? left.push(current) : right.push(current);'
expect(matches[0].lineTextOffset).toBe 0
expect(matches[0].leadingContextLines.length).toBe 0
expect(matches[0].trailingContextLines.length).toBe 0

expect(matches[1].matchText).toBe 'current'
expect(matches[1].range).toEqual [[6, 34], [6, 41]]
expect(matches[1].lineText).toBe ' current < pivot ? left.push(current) : right.push(current);'
expect(matches[1].lineTextOffset).toBe 0
expect(matches[1].leadingContextLines.length).toBe 0
expect(matches[1].trailingContextLines.length).toBe 0

describe "::scanInRange(range, regex, fn)", ->
beforeEach ->
Expand Down
40 changes: 28 additions & 12 deletions src/match-iterator.coffee
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
_ = require 'underscore-plus'
Point = require './point'
Range = require './range'

addContextLinesToCallbackArgument = (argument, options) ->
argument.leadingContextLines = []
row = Math.max(0, argument.range.start.row - (options.leadingContextLineCount or 0))
while row < argument.range.start.row
argument.leadingContextLines.push(argument.buffer.lineForRow(row))
row += 1

argument.trailingContextLines = []
for i in [0...(options.trailingContextLineCount or 0)]
row = argument.range.start.row + i + 1
break if row >= argument.buffer.getLineCount()
argument.trailingContextLines.push(argument.buffer.lineForRow(row))

class SingleLineSearchCallbackArgument
lineTextOffset: 0

Expand All @@ -16,9 +30,10 @@ class SingleLineSearchCallbackArgument
Object.defineProperty this.prototype, 'lineText',
get: -> @buffer.lineForRow(@row)

constructor: (@buffer, @row, @match, @lineOffset) ->
constructor: (@buffer, @row, @match, @lineOffset, options={}) ->
@stopped = false
@matchText = @match[0]
addContextLinesToCallbackArgument(this, options)

replace: (text) =>
@replacementText = text
Expand All @@ -27,7 +42,7 @@ class SingleLineSearchCallbackArgument
stop: => @stopped = true

class ForwardsSingleLine
constructor: (@buffer, @regex, @range) ->
constructor: (@buffer, @regex, @range, @options={}) ->

iterate: (callback, global) ->
row = @range.start.row
Expand All @@ -37,7 +52,7 @@ class ForwardsSingleLine

while row < @range.end.row
if match = @regex.exec(line)
argument = new SingleLineSearchCallbackArgument(@buffer, row, match, lineOffset)
argument = new SingleLineSearchCallbackArgument(@buffer, row, match, lineOffset, @options)
callback(argument)
return if argument.stopped or not global
if argument.replacementText?
Expand All @@ -53,7 +68,7 @@ class ForwardsSingleLine
line = line.slice(0, @range.end.column - lineOffset)
while match = @regex.exec(line)
break if line.length isnt 0 and match.index is @range.end.column
argument = new SingleLineSearchCallbackArgument(@buffer, row, match, lineOffset)
argument = new SingleLineSearchCallbackArgument(@buffer, row, match, lineOffset, @options)
callback(argument)
return if argument.stopped or not global
if argument.replacementText?
Expand All @@ -63,7 +78,7 @@ class ForwardsSingleLine
return

class BackwardsSingleLine
constructor: (@buffer, @regex, @range) ->
constructor: (@buffer, @regex, @range, @options={}) ->

iterate: (callback, global) ->
row = @range.end.row
Expand All @@ -78,7 +93,7 @@ class BackwardsSingleLine
@regex.lastIndex++
else
while match = bufferedMatches.pop()
argument = new SingleLineSearchCallbackArgument(@buffer, row, match, 0)
argument = new SingleLineSearchCallbackArgument(@buffer, row, match, 0, @options)
callback(argument)
return if argument.stopped or not global
row--
Expand All @@ -93,7 +108,7 @@ class BackwardsSingleLine
@regex.lastIndex++

while match = bufferedMatches.pop()
argument = new SingleLineSearchCallbackArgument(@buffer, row, match, 0)
argument = new SingleLineSearchCallbackArgument(@buffer, row, match, 0, @options)
callback(argument)
return if argument.stopped or not global
return
Expand All @@ -119,10 +134,11 @@ class MultiLineSearchCallbackArgument
Object.defineProperty this.prototype, 'lineText',
get: -> @buffer.lineForRow(@range.start.row)

constructor: (@buffer, @match, @lengthDelta) ->
constructor: (@buffer, @match, @lengthDelta, options={}) ->
@stopped = false
@replacementText = null
@matchText = @match[0]
addContextLinesToCallbackArgument(this, options)

replace: (text) =>
@replacementText = text
Expand All @@ -132,7 +148,7 @@ class MultiLineSearchCallbackArgument
@stopped = true

class ForwardsMultiLine
constructor: (@buffer, @regex, range) ->
constructor: (@buffer, @regex, range, @options={}) ->
@startIndex = @buffer.characterIndexForPosition(range.start)
@endIndex = @buffer.characterIndexForPosition(range.end)
@rangeEndColumn = range.end.column
Expand All @@ -142,7 +158,7 @@ class ForwardsMultiLine
iterate: (callback, global) ->
lengthDelta = 0
while match = @next()
argument = new MultiLineSearchCallbackArgument(@buffer, match, lengthDelta)
argument = new MultiLineSearchCallbackArgument(@buffer, match, lengthDelta, @options)
callback(argument)
if argument.replacementText?
lengthDelta += argument.replacementText.length - argument.matchText.length
Expand All @@ -169,7 +185,7 @@ class ForwardsMultiLine
match

class BackwardsMultiLine
constructor: (@buffer, @regex, range, @chunkSize) ->
constructor: (@buffer, @regex, range, @chunkSize, @options={}) ->
@text = @buffer.getText()
@startIndex = @buffer.characterIndexForPosition(range.start)
@endIndex = @buffer.characterIndexForPosition(range.end)
Expand All @@ -181,7 +197,7 @@ class BackwardsMultiLine

iterate: (callback, global) ->
while match = @next()
argument = new MultiLineSearchCallbackArgument(@buffer, match, 0)
argument = new MultiLineSearchCallbackArgument(@buffer, match, 0, @options)
callback(argument)
break unless global and not argument.stopped
return
Expand Down
65 changes: 54 additions & 11 deletions src/text-buffer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -1099,43 +1099,77 @@ class TextBuffer
# {::backwardsScan} to avoid tripping over your own changes.
#
# * `regex` A {RegExp} to search for.
# * `options` (optional) {Object}
# * `leadingContextLineCount` {Number} default `0`; The number of lines before the
# matched line to include in the results object.
# * `trailingContextLineCount` {Number} default `0`; The number of lines after the
# matched line to include in the results object.
# * `iterator` A {Function} that's called on each match with an {Object}
# containing the following keys:
# * `match` The current regular expression match.
# * `matchText` A {String} with the text of the match.
# * `range` The {Range} of the match.
# * `stop` Call this {Function} to terminate the scan.
# * `replace` Call this {Function} with a {String} to replace the match.
scan: (regex, iterator) ->
@scanInRange(regex, @getRange(), iterator)
# * `leadingContextLines` An {Array} with `leadingContextLineCount` lines before the match.
# * `trailingContextLines` An {Array} with `trailingContextLineCount` lines after the match.
scan: (regex, options={}, iterator) ->
if _.isFunction(options)
iterator = options
options = {}

@scanInRange(regex, @getRange(), options, iterator)

# Public: Scan regular expression matches in the entire buffer in reverse
# order, calling the given iterator function on each match.
#
# * `regex` A {RegExp} to search for.
# * `options` (optional) {Object}
# * `leadingContextLineCount` {Number} default `0`; The number of lines before the
# matched line to include in the results object.
# * `trailingContextLineCount` {Number} default `0`; The number of lines after the
# matched line to include in the results object.
# * `iterator` A {Function} that's called on each match with an {Object}
# containing the following keys:
# * `match` The current regular expression match.
# * `matchText` A {String} with the text of the match.
# * `range` The {Range} of the match.
# * `stop` Call this {Function} to terminate the scan.
# * `replace` Call this {Function} with a {String} to replace the match.
backwardsScan: (regex, iterator) ->
@backwardsScanInRange(regex, @getRange(), iterator)
# * `leadingContextLines` An {Array} with `leadingContextLineCount` lines before the match.
# * `trailingContextLines` An {Array} with `trailingContextLineCount` lines after the match.
backwardsScan: (regex, options={}, iterator) ->
if _.isFunction(options)
iterator = options
options = {}

@backwardsScanInRange(regex, @getRange(), options, iterator)

# Public: Scan regular expression matches in a given range , calling the given
# iterator function on each match.
#
# * `regex` A {RegExp} to search for.
# * `range` A {Range} in which to search.
# * `options` (optional) {Object}
# * `leadingContextLineCount` {Number} default `0`; The number of lines before the
# matched line to include in the results object.
# * `trailingContextLineCount` {Number} default `0`; The number of lines after the
# matched line to include in the results object.
# * `callback` A {Function} that's called on each match with an {Object}
# containing the following keys:
# * `match` The current regular expression match.
# * `matchText` A {String} with the text of the match.
# * `range` The {Range} of the match.
# * `stop` Call this {Function} to terminate the scan.
# * `replace` Call this {Function} with a {String} to replace the match.
scanInRange: (regex, range, callback, reverse=false) ->
# * `leadingContextLines` An {Array} with `leadingContextLineCount` lines before the match.
# * `trailingContextLines` An {Array} with `trailingContextLineCount` lines after the match.
scanInRange: (regex, range, options={}, callback, reverse=false) ->
if _.isFunction(options)
reverse = callback
callback = options
options = {}

range = @clipRange(range)
global = regex.global
flags = "gm"
Expand All @@ -1144,14 +1178,14 @@ class TextBuffer

if regexIsSingleLine(regex)
if reverse
iterator = new MatchIterator.BackwardsSingleLine(this, regex, range)
iterator = new MatchIterator.BackwardsSingleLine(this, regex, range, options)
else
iterator = new MatchIterator.ForwardsSingleLine(this, regex, range)
iterator = new MatchIterator.ForwardsSingleLine(this, regex, range, options)
else
if reverse
iterator = new MatchIterator.BackwardsMultiLine(this, regex, range, @backwardsScanChunkSize)
iterator = new MatchIterator.BackwardsMultiLine(this, regex, range, @backwardsScanChunkSize, options)
else
iterator = new MatchIterator.ForwardsMultiLine(this, regex, range)
iterator = new MatchIterator.ForwardsMultiLine(this, regex, range, options)

iterator.iterate(callback, global)

Expand All @@ -1160,15 +1194,24 @@ class TextBuffer
#
# * `regex` A {RegExp} to search for.
# * `range` A {Range} in which to search.
# * `options` (optional) {Object}
# * `leadingContextLineCount` {Number} default `0`; The number of lines before the
# matched line to include in the results object.
# * `trailingContextLineCount` {Number} default `0`; The number of lines after the
# matched line to include in the results object.
# * `iterator` A {Function} that's called on each match with an {Object}
# containing the following keys:
# * `match` The current regular expression match.
# * `matchText` A {String} with the text of the match.
# * `range` The {Range} of the match.
# * `stop` Call this {Function} to terminate the scan.
# * `replace` Call this {Function} with a {String} to replace the match.
backwardsScanInRange: (regex, range, iterator) ->
@scanInRange regex, range, iterator, true
backwardsScanInRange: (regex, range, options={}, iterator) ->
if _.isFunction(options)
iterator = options
options = {}

@scanInRange regex, range, options, iterator, true

# Public: Replace all regular expression matches in the entire buffer.
#
Expand Down

0 comments on commit a594c2b

Please sign in to comment.