text-buffer
Version:
A container for large mutable strings with annotated regions
462 lines (411 loc) • 14.5 kB
JavaScript
(function() {
var Delegator, Emitter, Grim, Marker, OptionKeys, Point, Range, extend, isEqual, omit, pick, ref, size;
ref = require('underscore-plus'), extend = ref.extend, isEqual = ref.isEqual, omit = ref.omit, pick = ref.pick, size = ref.size;
Emitter = require('event-kit').Emitter;
Delegator = require('delegato');
Point = require('./point');
Range = require('./range');
Grim = require('grim');
OptionKeys = new Set(['reversed', 'tailed', 'invalidate', 'exclusive']);
module.exports = Marker = (function() {
Delegator.includeInto(Marker);
Marker.extractParams = function(inputParams) {
var containsCustomProperties, i, key, len, outputParams, ref1;
outputParams = {};
containsCustomProperties = false;
if (inputParams != null) {
ref1 = Object.keys(inputParams);
for (i = 0, len = ref1.length; i < len; i++) {
key = ref1[i];
if (OptionKeys.has(key)) {
outputParams[key] = inputParams[key];
} else if (key === 'clipDirection' || key === 'skipSoftWrapIndentation') {
} else {
containsCustomProperties = true;
if (outputParams.properties == null) {
outputParams.properties = {};
}
outputParams.properties[key] = inputParams[key];
}
}
}
if (containsCustomProperties) {
Grim.deprecate("Assigning custom properties to a marker when creating/copying it is\ndeprecated. Please, consider storing the custom properties you need in\nsome other object in your package, keyed by the marker's id property.");
}
return outputParams;
};
Marker.delegatesMethods('containsPoint', 'containsRange', 'intersectsRow', {
toMethod: 'getRange'
});
function Marker(id, layer, range, params, exclusivitySet) {
this.id = id;
this.layer = layer;
if (exclusivitySet == null) {
exclusivitySet = false;
}
this.tailed = params.tailed, this.reversed = params.reversed, this.valid = params.valid, this.invalidate = params.invalidate, this.exclusive = params.exclusive, this.properties = params.properties;
this.emitter = new Emitter;
if (this.tailed == null) {
this.tailed = true;
}
if (this.reversed == null) {
this.reversed = false;
}
if (this.valid == null) {
this.valid = true;
}
if (this.invalidate == null) {
this.invalidate = 'overlap';
}
if (this.properties == null) {
this.properties = {};
}
this.hasChangeObservers = false;
Object.freeze(this.properties);
if (!exclusivitySet) {
this.layer.setMarkerIsExclusive(this.id, this.isExclusive());
}
}
/*
Section: Event Subscription
*/
Marker.prototype.onDidDestroy = function(callback) {
this.layer.markersWithDestroyListeners.add(this);
return this.emitter.on('did-destroy', callback);
};
Marker.prototype.onDidChange = function(callback) {
if (!this.hasChangeObservers) {
this.previousEventState = this.getSnapshot(this.getRange());
this.hasChangeObservers = true;
this.layer.markersWithChangeListeners.add(this);
}
return this.emitter.on('did-change', callback);
};
Marker.prototype.getRange = function() {
return this.layer.getMarkerRange(this.id);
};
Marker.prototype.setRange = function(range, params) {
if (params == null) {
params = {};
}
return this.update(this.getRange(), {
reversed: params.reversed,
tailed: true,
range: Range.fromObject(range, true),
exclusive: params.exclusive
});
};
Marker.prototype.getHeadPosition = function() {
if (this.reversed) {
return this.getStartPosition();
} else {
return this.getEndPosition();
}
};
Marker.prototype.setHeadPosition = function(position) {
var oldRange, params;
position = Point.fromObject(position);
oldRange = this.getRange();
params = {};
if (this.hasTail()) {
if (this.isReversed()) {
if (position.isLessThan(oldRange.end)) {
params.range = new Range(position, oldRange.end);
} else {
params.reversed = false;
params.range = new Range(oldRange.end, position);
}
} else {
if (position.isLessThan(oldRange.start)) {
params.reversed = true;
params.range = new Range(position, oldRange.start);
} else {
params.range = new Range(oldRange.start, position);
}
}
} else {
params.range = new Range(position, position);
}
return this.update(oldRange, params);
};
Marker.prototype.getTailPosition = function() {
if (this.reversed) {
return this.getEndPosition();
} else {
return this.getStartPosition();
}
};
Marker.prototype.setTailPosition = function(position) {
var oldRange, params;
position = Point.fromObject(position);
oldRange = this.getRange();
params = {
tailed: true
};
if (this.reversed) {
if (position.isLessThan(oldRange.start)) {
params.reversed = false;
params.range = new Range(position, oldRange.start);
} else {
params.range = new Range(oldRange.start, position);
}
} else {
if (position.isLessThan(oldRange.end)) {
params.range = new Range(position, oldRange.end);
} else {
params.reversed = true;
params.range = new Range(oldRange.end, position);
}
}
return this.update(oldRange, params);
};
Marker.prototype.getStartPosition = function() {
return this.layer.getMarkerStartPosition(this.id);
};
Marker.prototype.getEndPosition = function() {
return this.layer.getMarkerEndPosition(this.id);
};
Marker.prototype.clearTail = function() {
var headPosition;
headPosition = this.getHeadPosition();
return this.update(this.getRange(), {
tailed: false,
reversed: false,
range: Range(headPosition, headPosition)
});
};
Marker.prototype.plantTail = function() {
var headPosition;
if (!this.hasTail()) {
headPosition = this.getHeadPosition();
return this.update(this.getRange(), {
tailed: true,
range: new Range(headPosition, headPosition)
});
}
};
Marker.prototype.isReversed = function() {
return this.tailed && this.reversed;
};
Marker.prototype.hasTail = function() {
return this.tailed;
};
Marker.prototype.isValid = function() {
return !this.isDestroyed() && this.valid;
};
Marker.prototype.isDestroyed = function() {
return !this.layer.hasMarker(this.id);
};
Marker.prototype.isExclusive = function() {
if (this.exclusive != null) {
return this.exclusive;
} else {
return this.getInvalidationStrategy() === 'inside' || !this.hasTail();
}
};
Marker.prototype.isEqual = function(other) {
return this.invalidate === other.invalidate && this.tailed === other.tailed && this.reversed === other.reversed && this.exclusive === other.exclusive && isEqual(this.properties, other.properties) && this.getRange().isEqual(other.getRange());
};
Marker.prototype.getInvalidationStrategy = function() {
return this.invalidate;
};
Marker.prototype.getProperties = function() {
return this.properties;
};
Marker.prototype.setProperties = function(properties) {
return this.update(this.getRange(), {
properties: extend({}, this.properties, properties)
});
};
Marker.prototype.copy = function(options) {
var snapshot;
if (options == null) {
options = {};
}
snapshot = this.getSnapshot();
options = Marker.extractParams(options);
return this.layer.createMarker(this.getRange(), extend({}, snapshot, options, {
properties: extend({}, snapshot.properties, options.properties)
}));
};
Marker.prototype.destroy = function(suppressMarkerLayerUpdateEvents) {
var error;
if (this.isDestroyed()) {
return;
}
if (this.trackDestruction) {
error = new Error;
Error.captureStackTrace(error);
this.destroyStackTrace = error.stack;
}
this.layer.destroyMarker(this, suppressMarkerLayerUpdateEvents);
this.emitter.emit('did-destroy');
return this.emitter.clear();
};
Marker.prototype.compare = function(other) {
return this.layer.compareMarkers(this.id, other.id);
};
Marker.prototype.matchesParams = function(params) {
var i, key, len, ref1;
ref1 = Object.keys(params);
for (i = 0, len = ref1.length; i < len; i++) {
key = ref1[i];
if (!this.matchesParam(key, params[key])) {
return false;
}
}
return true;
};
Marker.prototype.matchesParam = function(key, value) {
switch (key) {
case 'startPosition':
return this.getStartPosition().isEqual(value);
case 'endPosition':
return this.getEndPosition().isEqual(value);
case 'containsPoint':
case 'containsPosition':
return this.containsPoint(value);
case 'containsRange':
return this.containsRange(value);
case 'startRow':
return this.getStartPosition().row === value;
case 'endRow':
return this.getEndPosition().row === value;
case 'intersectsRow':
return this.intersectsRow(value);
case 'invalidate':
case 'reversed':
case 'tailed':
return isEqual(this[key], value);
case 'valid':
return this.isValid() === value;
default:
return isEqual(this.properties[key], value);
}
};
Marker.prototype.update = function(oldRange, arg, textChanged, suppressMarkerLayerUpdateEvents) {
var exclusive, properties, propertiesChanged, range, reversed, tailed, updated, valid, wasExclusive;
range = arg.range, reversed = arg.reversed, tailed = arg.tailed, valid = arg.valid, exclusive = arg.exclusive, properties = arg.properties;
if (textChanged == null) {
textChanged = false;
}
if (suppressMarkerLayerUpdateEvents == null) {
suppressMarkerLayerUpdateEvents = false;
}
if (this.isDestroyed()) {
return;
}
oldRange = Range.fromObject(oldRange);
if (range != null) {
range = Range.fromObject(range);
}
wasExclusive = this.isExclusive();
updated = propertiesChanged = false;
if ((range != null) && !range.isEqual(oldRange)) {
this.layer.setMarkerRange(this.id, range);
updated = true;
}
if ((reversed != null) && reversed !== this.reversed) {
this.reversed = reversed;
updated = true;
}
if ((tailed != null) && tailed !== this.tailed) {
this.tailed = tailed;
updated = true;
}
if ((valid != null) && valid !== this.valid) {
this.valid = valid;
updated = true;
}
if ((exclusive != null) && exclusive !== this.exclusive) {
this.exclusive = exclusive;
updated = true;
}
if (wasExclusive !== this.isExclusive()) {
this.layer.setMarkerIsExclusive(this.id, this.isExclusive());
updated = true;
}
if ((properties != null) && !isEqual(properties, this.properties)) {
this.properties = Object.freeze(properties);
propertiesChanged = true;
updated = true;
}
this.emitChangeEvent(range != null ? range : oldRange, textChanged, propertiesChanged);
if (updated && !suppressMarkerLayerUpdateEvents) {
this.layer.markerUpdated();
}
return updated;
};
Marker.prototype.getSnapshot = function(range, includeMarker) {
var snapshot;
if (includeMarker == null) {
includeMarker = true;
}
snapshot = {
range: range,
properties: this.properties,
reversed: this.reversed,
tailed: this.tailed,
valid: this.valid,
invalidate: this.invalidate,
exclusive: this.exclusive
};
if (includeMarker) {
snapshot.marker = this;
}
return Object.freeze(snapshot);
};
Marker.prototype.toString = function() {
return "[Marker " + this.id + ", " + (this.getRange()) + "]";
};
/*
Section: Private
*/
Marker.prototype.inspect = function() {
return this.toString();
};
Marker.prototype.emitChangeEvent = function(currentRange, textChanged, propertiesChanged) {
var newHeadPosition, newState, newTailPosition, oldHeadPosition, oldState, oldTailPosition;
if (!this.hasChangeObservers) {
return;
}
oldState = this.previousEventState;
if (currentRange == null) {
currentRange = this.getRange();
}
if (!(propertiesChanged || oldState.valid !== this.valid || oldState.tailed !== this.tailed || oldState.reversed !== this.reversed || oldState.range.compare(currentRange) !== 0)) {
return false;
}
newState = this.previousEventState = this.getSnapshot(currentRange);
if (oldState.reversed) {
oldHeadPosition = oldState.range.start;
oldTailPosition = oldState.range.end;
} else {
oldHeadPosition = oldState.range.end;
oldTailPosition = oldState.range.start;
}
if (newState.reversed) {
newHeadPosition = newState.range.start;
newTailPosition = newState.range.end;
} else {
newHeadPosition = newState.range.end;
newTailPosition = newState.range.start;
}
this.emitter.emit("did-change", {
wasValid: oldState.valid,
isValid: newState.valid,
hadTail: oldState.tailed,
hasTail: newState.tailed,
oldProperties: oldState.properties,
newProperties: newState.properties,
oldHeadPosition: oldHeadPosition,
newHeadPosition: newHeadPosition,
oldTailPosition: oldTailPosition,
newTailPosition: newTailPosition,
textChanged: textChanged
});
return true;
};
return Marker;
})();
}).call(this);