Point = require 'point' _ = require 'underscore' # Public: Indicates a region within the editor. # # To better visualize how this works, imagine a rectangle. # Each quadrant of the rectangle is analogus to a range, as ranges contain a # starting row and a starting column, as well as an ending row, and an ending column. # # Each `Range` is actually constructed of two `Point` objects, labelled `start` and `end`. module.exports = class Range # Constructs a `Range` from a given object. # # object - This can be an {Array} (`[startRow, startColumn, endRow, endColumn]`) or an object `{start: Point, end: Point}` # # Returns the new {Range}. @fromObject: (object) -> if _.isArray(object) new Range(object...) else if object instanceof Range object else new Range(object.start, object.end) # Constructs a `Range` from a {Point}, and the delta values beyond that point. # # point - A {Point} to start with # rowDelta - A {Number} indicating how far from the starting {Point} the range's row should be # 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) new Range(pointA, pointB) # Creates a new `Range` object based on two {Point}s. # # pointA - The first {Point} (default: `0, 0`) # pointB - The second {Point} (default: `0, 0`) constructor: (pointA = new Point(0, 0), pointB = new Point(0, 0)) -> pointA = Point.fromObject(pointA) pointB = Point.fromObject(pointB) if pointA.compare(pointB) <= 0 @start = pointA @end = pointB else @start = pointB @end = pointA # Creates an identical copy of the `Range`. # # Returns a duplicate {Range}. copy: -> new Range(@start.copy(), @end.copy()) # Identifies if two `Range`s are equal. # # All four points (`start.row`, `start.column`, `end.row`, `end.column`) must be # equal for this method to return `true`. # # other - A different {Range} to check against # # Returns a {Boolean}. isEqual: (other) -> if _.isArray(other) and other.length == 2 other = new Range(other...) other.start.isEqual(@start) and other.end.isEqual(@end) # Returns an integer (-1, 0, 1) indicating whether this range is less than, equal, # or greater than the given range when sorting. # # Ranges that start earlier are considered "less than" ranges that start later. # If ranges start at the same location, the larger range sorts before the smaller # range. # # other - A {Range} to compare against. # # Returns a {Number}, either -1, 0, or 1. compare: (other) -> other = Range.fromObject(other) if value = @start.compare(other.start) value else other.end.compare(@end) # Identifies if the `Range` is on the same line. # # In other words, if `start.row` is equal to `end.row`. # # Returns a {Boolean}. isSingleLine: -> @start.row == @end.row # Identifies if two `Range`s are on the same line. # # other - A different {Range} to check against # # Returns a {Boolean}. coversSameRows: (other) -> @start.row == other.start.row && @end.row == other.end.row # Adds a new point to the `Range`s `start` and `end`. # # point - A new {Point} to add # # Returns the new {Range}. add: (point) -> new Range(@start.add(point), @end.add(point)) # Moves a `Range`. # # In other words, the starting and ending `row` values, and the starting and ending # `column` values, are added to each other. # # startPoint - The {Point} to move the `Range`s `start` by # endPoint - The {Point} to move the `Range`s `end` by # # Returns the new {Range}. translate: (startPoint, endPoint=startPoint) -> new Range(@start.translate(startPoint), @end.translate(endPoint)) # Identifies if two `Range`s intersect each other. # # otherRange - A different {Range} to check against # # Returns a {Boolean}. intersectsWith: (otherRange) -> if @start.isLessThanOrEqual(otherRange.start) @end.isGreaterThanOrEqual(otherRange.start) else otherRange.intersectsWith(this) # Identifies if a second `Range` is contained within a first. # # otherRange - A different {Range} to check against # options - A hash with a single option: # exclusive: A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal # # Returns a {Boolean}. containsRange: (otherRange, {exclusive} = {}) -> { start, end } = Range.fromObject(otherRange) @containsPoint(start, {exclusive}) and @containsPoint(end, {exclusive}) # Identifies if a `Range` contains a {Point}. # # point - A {Point} to check against # options - A hash with a single option: # exclusive: A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal # # Returns a {Boolean}. containsPoint: (point, {exclusive} = {}) -> point = Point.fromObject(point) if exclusive point.isGreaterThan(@start) and point.isLessThan(@end) else point.isGreaterThanOrEqual(@start) and point.isLessThanOrEqual(@end) # Identifies if a `Range` contains a row. # # row - A row {Number} to check against # options - A hash with a single option: # # Returns a {Boolean}. containsRow: (row) -> @start.row <= row <= @end.row # Constructs a union between two `Range`s. # # otherRange - A different {Range} to unionize with # # Returns the new {Range}. union: (otherRange) -> start = if @start.isLessThan(otherRange.start) then @start else otherRange.start end = if @end.isGreaterThan(otherRange.end) then @end else otherRange.end new Range(start, end) # Identifies if a `Range` is empty. # # A `Range` is empty if its start {Point} matches its end. # # Returns a {Boolean}. isEmpty: -> @start.isEqual(@end) # Calculates the difference between a `Range`s `start` and `end` points. # # Returns a {Point}. toDelta: -> rows = @end.row - @start.row if rows == 0 columns = @end.column - @start.column else columns = @end.column new Point(rows, columns) # Calculates the number of rows a `Range`s contains. # # Returns a {Number}. getRowCount: -> @end.row - @start.row + 1 # Returns an array of all rows in a `Range` # # Returns an {Array} getRows: -> [@start.row..@end.row] ### Internal ### inspect: -> "[#{@start.inspect()} - #{@end.inspect()}]"