editor-document
Version:
applyDelta, Delta, Document, Position, Range
1,125 lines (1,095 loc) • 65 kB
JavaScript
System.register([], (function (exports) {
'use strict';
return {
execute: (function () {
exports({
applyDelta: applyDelta,
comparePositions: comparePositions,
equalPositions: equalPositions,
isEmptyRange: isEmptyRange,
position: position,
range: range
});
function applyDelta(docLines, delta) {
const startRow = delta.start.row;
const startColumn = delta.start.column;
const line = docLines[startRow] || "";
switch (delta.action) {
case "insert": {
const lines = delta.lines;
if (lines.length === 1) {
docLines[startRow] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn);
}
else {
// The following three lines are replaced by the one that follows because eslint prefer-spread.
// let args: unknown[] = [startRow, 1];
// args = args.concat(delta.lines);
// docLines.splice.apply(docLines, args);
docLines.splice(startRow, 1, ...delta.lines);
docLines[startRow] = line.substring(0, startColumn) + docLines[startRow];
docLines[startRow + delta.lines.length - 1] += line.substring(startColumn);
}
break;
}
case "remove": {
const endColumn = delta.end.column;
const endRow = delta.end.row;
if (startRow === endRow) {
docLines[startRow] = line.substring(0, startColumn) + line.substring(endColumn);
}
else {
docLines.splice(startRow, endRow - startRow + 1, line.substring(0, startColumn) + docLines[endRow].substring(endColumn));
}
break;
}
}
}
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
function isFunction(value) {
return typeof value === 'function';
}
function createErrorClass(createImpl) {
var _super = function (instance) {
Error.call(instance);
instance.stack = new Error().stack;
};
var ctorFunc = createImpl(_super);
ctorFunc.prototype = Object.create(Error.prototype);
ctorFunc.prototype.constructor = ctorFunc;
return ctorFunc;
}
var UnsubscriptionError = createErrorClass(function (_super) {
return function UnsubscriptionErrorImpl(errors) {
_super(this);
this.message = errors
? errors.length + " errors occurred during unsubscription:\n" + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n ')
: '';
this.name = 'UnsubscriptionError';
this.errors = errors;
};
});
function arrRemove(arr, item) {
if (arr) {
var index = arr.indexOf(item);
0 <= index && arr.splice(index, 1);
}
}
var Subscription = (function () {
function Subscription(initialTeardown) {
this.initialTeardown = initialTeardown;
this.closed = false;
this._parentage = null;
this._finalizers = null;
}
Subscription.prototype.unsubscribe = function () {
var e_1, _a, e_2, _b;
var errors;
if (!this.closed) {
this.closed = true;
var _parentage = this._parentage;
if (_parentage) {
this._parentage = null;
if (Array.isArray(_parentage)) {
try {
for (var _parentage_1 = __values(_parentage), _parentage_1_1 = _parentage_1.next(); !_parentage_1_1.done; _parentage_1_1 = _parentage_1.next()) {
var parent_1 = _parentage_1_1.value;
parent_1.remove(this);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_parentage_1_1 && !_parentage_1_1.done && (_a = _parentage_1.return)) _a.call(_parentage_1);
}
finally { if (e_1) throw e_1.error; }
}
}
else {
_parentage.remove(this);
}
}
var initialFinalizer = this.initialTeardown;
if (isFunction(initialFinalizer)) {
try {
initialFinalizer();
}
catch (e) {
errors = e instanceof UnsubscriptionError ? e.errors : [e];
}
}
var _finalizers = this._finalizers;
if (_finalizers) {
this._finalizers = null;
try {
for (var _finalizers_1 = __values(_finalizers), _finalizers_1_1 = _finalizers_1.next(); !_finalizers_1_1.done; _finalizers_1_1 = _finalizers_1.next()) {
var finalizer = _finalizers_1_1.value;
try {
execFinalizer(finalizer);
}
catch (err) {
errors = errors !== null && errors !== void 0 ? errors : [];
if (err instanceof UnsubscriptionError) {
errors = __spreadArray(__spreadArray([], __read(errors)), __read(err.errors));
}
else {
errors.push(err);
}
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_finalizers_1_1 && !_finalizers_1_1.done && (_b = _finalizers_1.return)) _b.call(_finalizers_1);
}
finally { if (e_2) throw e_2.error; }
}
}
if (errors) {
throw new UnsubscriptionError(errors);
}
}
};
Subscription.prototype.add = function (teardown) {
var _a;
if (teardown && teardown !== this) {
if (this.closed) {
execFinalizer(teardown);
}
else {
if (teardown instanceof Subscription) {
if (teardown.closed || teardown._hasParent(this)) {
return;
}
teardown._addParent(this);
}
(this._finalizers = (_a = this._finalizers) !== null && _a !== void 0 ? _a : []).push(teardown);
}
}
};
Subscription.prototype._hasParent = function (parent) {
var _parentage = this._parentage;
return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));
};
Subscription.prototype._addParent = function (parent) {
var _parentage = this._parentage;
this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;
};
Subscription.prototype._removeParent = function (parent) {
var _parentage = this._parentage;
if (_parentage === parent) {
this._parentage = null;
}
else if (Array.isArray(_parentage)) {
arrRemove(_parentage, parent);
}
};
Subscription.prototype.remove = function (teardown) {
var _finalizers = this._finalizers;
_finalizers && arrRemove(_finalizers, teardown);
if (teardown instanceof Subscription) {
teardown._removeParent(this);
}
};
Subscription.EMPTY = (function () {
var empty = new Subscription();
empty.closed = true;
return empty;
})();
return Subscription;
}());
Subscription.EMPTY;
function isSubscription(value) {
return (value instanceof Subscription ||
(value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe)));
}
function execFinalizer(finalizer) {
if (isFunction(finalizer)) {
finalizer();
}
else {
finalizer.unsubscribe();
}
}
var config = {
onUnhandledError: null,
onStoppedNotification: null,
Promise: undefined,
useDeprecatedSynchronousErrorHandling: false,
useDeprecatedNextContext: false,
};
var timeoutProvider = {
setTimeout: function (handler, timeout) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
return setTimeout.apply(void 0, __spreadArray([handler, timeout], __read(args)));
},
clearTimeout: function (handle) {
var delegate = timeoutProvider.delegate;
return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearTimeout) || clearTimeout)(handle);
},
delegate: undefined,
};
function reportUnhandledError(err) {
timeoutProvider.setTimeout(function () {
{
throw err;
}
});
}
function noop() { }
function errorContext(cb) {
{
cb();
}
}
var Subscriber = (function (_super) {
__extends(Subscriber, _super);
function Subscriber(destination) {
var _this = _super.call(this) || this;
_this.isStopped = false;
if (destination) {
_this.destination = destination;
if (isSubscription(destination)) {
destination.add(_this);
}
}
else {
_this.destination = EMPTY_OBSERVER;
}
return _this;
}
Subscriber.create = function (next, error, complete) {
return new SafeSubscriber(next, error, complete);
};
Subscriber.prototype.next = function (value) {
if (this.isStopped) ;
else {
this._next(value);
}
};
Subscriber.prototype.error = function (err) {
if (this.isStopped) ;
else {
this.isStopped = true;
this._error(err);
}
};
Subscriber.prototype.complete = function () {
if (this.isStopped) ;
else {
this.isStopped = true;
this._complete();
}
};
Subscriber.prototype.unsubscribe = function () {
if (!this.closed) {
this.isStopped = true;
_super.prototype.unsubscribe.call(this);
this.destination = null;
}
};
Subscriber.prototype._next = function (value) {
this.destination.next(value);
};
Subscriber.prototype._error = function (err) {
try {
this.destination.error(err);
}
finally {
this.unsubscribe();
}
};
Subscriber.prototype._complete = function () {
try {
this.destination.complete();
}
finally {
this.unsubscribe();
}
};
return Subscriber;
}(Subscription));
var _bind = Function.prototype.bind;
function bind(fn, thisArg) {
return _bind.call(fn, thisArg);
}
var ConsumerObserver = (function () {
function ConsumerObserver(partialObserver) {
this.partialObserver = partialObserver;
}
ConsumerObserver.prototype.next = function (value) {
var partialObserver = this.partialObserver;
if (partialObserver.next) {
try {
partialObserver.next(value);
}
catch (error) {
handleUnhandledError(error);
}
}
};
ConsumerObserver.prototype.error = function (err) {
var partialObserver = this.partialObserver;
if (partialObserver.error) {
try {
partialObserver.error(err);
}
catch (error) {
handleUnhandledError(error);
}
}
else {
handleUnhandledError(err);
}
};
ConsumerObserver.prototype.complete = function () {
var partialObserver = this.partialObserver;
if (partialObserver.complete) {
try {
partialObserver.complete();
}
catch (error) {
handleUnhandledError(error);
}
}
};
return ConsumerObserver;
}());
var SafeSubscriber = (function (_super) {
__extends(SafeSubscriber, _super);
function SafeSubscriber(observerOrNext, error, complete) {
var _this = _super.call(this) || this;
var partialObserver;
if (isFunction(observerOrNext) || !observerOrNext) {
partialObserver = {
next: (observerOrNext !== null && observerOrNext !== void 0 ? observerOrNext : undefined),
error: error !== null && error !== void 0 ? error : undefined,
complete: complete !== null && complete !== void 0 ? complete : undefined,
};
}
else {
var context_1;
if (_this && config.useDeprecatedNextContext) {
context_1 = Object.create(observerOrNext);
context_1.unsubscribe = function () { return _this.unsubscribe(); };
partialObserver = {
next: observerOrNext.next && bind(observerOrNext.next, context_1),
error: observerOrNext.error && bind(observerOrNext.error, context_1),
complete: observerOrNext.complete && bind(observerOrNext.complete, context_1),
};
}
else {
partialObserver = observerOrNext;
}
}
_this.destination = new ConsumerObserver(partialObserver);
return _this;
}
return SafeSubscriber;
}(Subscriber));
function handleUnhandledError(error) {
{
reportUnhandledError(error);
}
}
function defaultErrorHandler(err) {
throw err;
}
var EMPTY_OBSERVER = {
closed: true,
next: noop,
error: defaultErrorHandler,
complete: noop,
};
var observable = (function () { return (typeof Symbol === 'function' && Symbol.observable) || '@@observable'; })();
function identity(x) {
return x;
}
function pipeFromArray(fns) {
if (fns.length === 0) {
return identity;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input) {
return fns.reduce(function (prev, fn) { return fn(prev); }, input);
};
}
var Observable = (function () {
function Observable(subscribe) {
if (subscribe) {
this._subscribe = subscribe;
}
}
Observable.prototype.lift = function (operator) {
var observable = new Observable();
observable.source = this;
observable.operator = operator;
return observable;
};
Observable.prototype.subscribe = function (observerOrNext, error, complete) {
var _this = this;
var subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);
errorContext(function () {
var _a = _this, operator = _a.operator, source = _a.source;
subscriber.add(operator
?
operator.call(subscriber, source)
: source
?
_this._subscribe(subscriber)
:
_this._trySubscribe(subscriber));
});
return subscriber;
};
Observable.prototype._trySubscribe = function (sink) {
try {
return this._subscribe(sink);
}
catch (err) {
sink.error(err);
}
};
Observable.prototype.forEach = function (next, promiseCtor) {
var _this = this;
promiseCtor = getPromiseCtor(promiseCtor);
return new promiseCtor(function (resolve, reject) {
var subscriber = new SafeSubscriber({
next: function (value) {
try {
next(value);
}
catch (err) {
reject(err);
subscriber.unsubscribe();
}
},
error: reject,
complete: resolve,
});
_this.subscribe(subscriber);
});
};
Observable.prototype._subscribe = function (subscriber) {
var _a;
return (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber);
};
Observable.prototype[observable] = function () {
return this;
};
Observable.prototype.pipe = function () {
var operations = [];
for (var _i = 0; _i < arguments.length; _i++) {
operations[_i] = arguments[_i];
}
return pipeFromArray(operations)(this);
};
Observable.prototype.toPromise = function (promiseCtor) {
var _this = this;
promiseCtor = getPromiseCtor(promiseCtor);
return new promiseCtor(function (resolve, reject) {
var value;
_this.subscribe(function (x) { return (value = x); }, function (err) { return reject(err); }, function () { return resolve(value); });
});
};
Observable.create = function (subscribe) {
return new Observable(subscribe);
};
return Observable;
}());
function getPromiseCtor(promiseCtor) {
var _a;
return (_a = promiseCtor !== null && promiseCtor !== void 0 ? promiseCtor : config.Promise) !== null && _a !== void 0 ? _a : Promise;
}
function isObserver(value) {
return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);
}
function isSubscriber(value) {
return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));
}
// import { refChange } from '@stemcstudio/common';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function refChange(uuid, moniker, chnage) {
}
/**
* (session)
*/
class EventBus {
/**
* Used for monitoring subscriptions.
*/
#uuid = `${Math.random()}`;
#moniker;
#monitor;
/**
* Each event name may have multiple callback functions.
*/
#subscribers;
/**
* In general, you don't want to supply the $monitor argument, letting it default to true.
*
* @param moniker A name that can be used to recognize this event bus.
* @param monitor Determines whether reference counting due to listeners will be performed.
* @param warning Determines whether a warning will be logged to the console.
*/
constructor(moniker, monitor, warning = monitor) {
this.#moniker = moniker;
this.#monitor = monitor;
if (warning && !monitor) {
// eslint-disable-next-line no-console
console.warn(`${moniker} has elected to bypass monitoring. It may be leaking subscriptions?`);
}
}
/**
* For testing purposes, we want to know whether unsubscribing is cleaning up the listeners.
* @param eventName The name of the event.
*/
subscriberCountByEvent(eventName) {
if (this.#subscribers) {
return this.#subscribers[eventName].length;
}
else {
return 0;
}
}
/**
* Synchronous dispatch of the event to subscribers.
*/
send(eventName, event) {
return this.dispatch(eventName, event);
}
/**
* Asynchronous dispatch of the event to subscribers.
*
* @param eventName The name of the event.
* @param event The value of the event.
* @param timeout The delay, in milliseconds, for emitting the event.
* @returns A promise that resolves when the event has been dispatched.
*/
post(eventName, event, timeout = 0) {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
try {
const count = this.dispatch(eventName, event);
try {
resolve(count);
}
catch (err) {
// Ignore.
// Perhaps there should be a callback parameter that allows this case to be handled?
}
}
catch (err) {
reject(err);
}
}, timeout);
});
}
/**
* Creates an Observable for one event type.
* @param eventName The name of the event that the observable is being created for.
*/
events(eventName) {
return new Observable((observer) => {
// TODO: Interesting that the source is not used.
// Does that mean that the source will not be present to the subscriber?
function next(value) {
observer.next(value);
}
// By returning the function that removes the event listener,
// unsubscribe() on the subscription to the Observable cleans up the subscribers.
return this.addEventListener(eventName, next);
});
}
/**
* @deprecated Use an observable
*/
on(eventName, callback) {
return this.addEventListener(eventName, callback);
}
/**
* @deprecated Use an Observable.
*/
off(eventName, callback) {
return this.removeEventListener(eventName, callback);
}
addEventListener(eventName, callback) {
if (this.#monitor) {
refChange(this.#uuid, this.#moniker);
}
this.#subscribers = this.#subscribers || {};
let listeners = this.#subscribers[eventName];
if (!listeners) {
listeners = this.#subscribers[eventName] = [];
}
if (listeners.indexOf(callback) === -1) {
listeners.push(callback);
}
return () => {
this.removeEventListener(eventName, callback);
};
}
removeEventListener(eventName, callback) {
if (this.#monitor) {
refChange(this.#uuid, this.#moniker);
}
this.#subscribers = this.#subscribers || {};
const listeners = this.#subscribers[eventName];
if (!listeners) {
return;
}
const index = listeners.indexOf(callback);
if (index !== -1) {
listeners.splice(index, 1);
}
}
/**
* Calls each listener subscribed to the eventName passing the event and the source.
* Returns the number of listeners who received the event.
*/
dispatch(eventName, event) {
/**
* The listeners subscribed to the specified event name
*/
let listeners = (this.#subscribers || {})[eventName];
if (!listeners) {
return 0;
}
// slice just makes a copy so that we don't mess up on array bounds.
// It's a bit expensive though?
listeners = listeners.slice();
let count = 0;
for (const listener of listeners) {
listener(event);
count += 1;
}
return count;
}
}
/**
* Constructs a position object from a zero-based row and zero-based column.
* @param row The zero-based row.
* @param column The zero-based column.
*/
function position(row, column) {
return { row, column };
}
/**
* Returns 0 if positions are equal, +1 if p1 comes after p2, -1 if p1 comes before p2.
*/
function comparePositions(p1, p2) {
if (p1.row > p2.row) {
return 1;
}
else if (p1.row < p2.row) {
return -1;
}
else {
if (p1.column > p2.column) {
return 1;
}
else if (p1.column < p2.column) {
return -1;
}
else {
return 0;
}
}
}
/**
* Determines whether positions p1 and p2 are equal.
* @param p1 The first position.
* @param p2 The second position.
*/
function equalPositions(p1, p2) {
return p1.row === p2.row && p1.column === p2.column;
}
function range(start, end) {
return { start, end };
}
/**
* The range is empty if the start and end position coincide.
*/
function isEmptyRange(range) {
return equalPositions(range.start, range.end);
}
/**
* Copies a Position.
*/
function clonePos(pos) {
return { row: pos.row, column: pos.column };
}
/**
* Constructs a Position from row and column.
*/
function pos(row, column) {
return { row: row, column: column };
}
const $split = (function () {
function foo(text) {
return text.replace(/\r\n|\r/g, "\n").split("\n");
}
function bar(text) {
return text.split(/\r\n|\r|\n/);
}
// Determine whether the split function performs as we expect.
// Here we attempt to separate a string of three separators.
// If all works out, we should get back an array of four (4) empty strings.
if ("aaa".split(/a/).length === 0) {
return foo;
}
else {
// In Chrome, this is the mainline because the result
// of the test condition length is 4.
return bar;
}
})();
/*
function clipPosition(doc: Document, position: Position): Position {
const length = doc.getLength();
if (position.row >= length) {
position.row = Math.max(0, length - 1);
position.column = doc.getLine(length - 1).length;
}
else {
position.row = Math.max(0, position.row);
position.column = Math.min(Math.max(position.column, 0), doc.getLine(position.row).length);
}
return position;
}
*/
const CHANGE = 'change';
const CHANGE_NEW_LINE_MODE = 'changeNewLineMode';
/**
*
*/
class Document {
/**
* The lines of text.
* These lines do not include a line terminating character.
*/
#lines = [];
/**
*
*/
#autoNewLine = "";
/**
*
*/
#newLineMode = "auto";
/**
* A source of 'change' events that is observable.
*/
/*
public readonly changeEvents: Observable<Delta>;
*/
/**
*
*/
#change = new EventBus('', true);
change$ = this.#change.events(CHANGE);
#changeNewLineMode = new EventBus('', true);
changeNewLineMode$ = this.#changeNewLineMode.events(CHANGE_NEW_LINE_MODE);
/**
* Maintains a count of the number of references to this instance of Document.
*/
#refCount = 1;
/**
* If text is included, the Document contains those strings; otherwise, it's empty.
* A `change` event will be emitted. But does anyone see it?
*
* @param textOrLines
*/
constructor(textOrLines) {
this.#lines = [""];
/*
this.changeEvents = new Observable<Delta>((observer: Observer<Delta>) => {
function changeListener(value: Delta, source: Document) {
observer.next(value);
}
this.addChangeListener(changeListener);
return () => {
this.removeChangeListener(changeListener);
};
});
*/
// There has to be one line at least in the document. If you pass an empty
// string to the insert function, nothing will happen. Workaround.
if (textOrLines.length === 0) {
this.#lines = [""];
}
else if (Array.isArray(textOrLines)) {
this.insertMergedLines({ row: 0, column: 0 }, textOrLines);
}
else {
this.insert({ row: 0, column: 0 }, textOrLines);
}
}
#destructor() {
this.#lines.length = 0;
}
addRef() {
this.#refCount++;
return this.#refCount;
}
release() {
this.#refCount--;
if (this.#refCount === 0) {
this.#destructor();
}
else if (this.#refCount < 0) {
throw new Error("Document refCount is negative.");
}
return this.#refCount;
}
/**
* Replaces all the lines in the current `Document` with the value of `text`.
* A `change` event will be emitted.
*/
setValue(text) {
const row = this.getLength() - 1;
const start = position(0, 0);
const end = position(row, this.getLine(row).length);
// FIXME: Can we avoid the temporary objects?
this.remove(range(start, end));
this.insert({ row: 0, column: 0 }, text);
}
/**
* Returns all the lines in the document as a single string, joined by the new line character.
*/
getValue() {
return this.#lines.join(this.getNewLineCharacter());
}
/**
* Determines the newline character that is present in the presented text
* and caches the result in $autoNewLine.
* Emits 'changeNewLineMode'.
*/
#detect_new_line(text) {
const match = text.match(/^.*?(\r\n|\r|\n)/m);
this.#autoNewLine = match ? match[1] : "\n";
this.#changeNewLineMode.send(CHANGE_NEW_LINE_MODE, this.#autoNewLine);
}
/**
* Returns the newline character that's being used, depending on the value of `newLineMode`.
* If `newLineMode == windows`, `\r\n` is returned.
* If `newLineMode == unix`, `\n` is returned.
* If `newLineMode == auto`, the value of `autoNewLine` is returned.
*/
getNewLineCharacter() {
switch (this.#newLineMode) {
case "windows":
return "\r\n";
case "unix":
return "\n";
default:
return this.#autoNewLine || "\n";
}
}
/**
* Sets the new line mode.
*
* newLineMode is the newline mode to use; can be either `windows`, `unix`, or `auto`.
* Emits 'changeNewLineMode'
*/
setNewLineMode(newLineMode) {
if (this.#newLineMode === newLineMode) {
return;
}
this.#newLineMode = newLineMode;
this.#changeNewLineMode.send(CHANGE_NEW_LINE_MODE, this.#newLineMode);
}
/**
* Returns the type of newlines being used; either `windows`, `unix`, or `auto`.
*/
getNewLineMode() {
return this.#newLineMode;
}
/**
* Returns `true` if `text` is a newline character (either `\r\n`, `\r`, or `\n`).
*
* @param text The text to check.
*/
isNewLine(text) {
return (text === "\r\n" || text === "\r" || text === "\n");
}
/**
* Returns a verbatim copy of the given line as it is in the document.
*
* @param row The row index to retrieve.
*/
getLine(row) {
return this.#lines[row] || "";
}
/**
* Returns a COPY of the lines between and including `firstRow` and `lastRow`.
* These lines do not include the line terminator.
*
* @param firstRow The first row index to retrieve.
* @param lastRow The final row index to retrieve.
*/
getLines(firstRow, lastRow) {
// The semantics of slice are that it does not include the end index.
const end = lastRow + 1;
return this.#lines.slice(firstRow, end);
}
/**
* Returns a COPY of the lines in the document.
* These lines do not include the line terminator.
*/
getAllLines() {
return this.#lines.slice(0, this.#lines.length);
}
/**
* Returns the number of rows in the document.
*/
getLength() {
return this.#lines.length;
}
/**
* Returns all the text corresponding to the range with line terminators.
*/
getTextRange(range) {
return this.getLinesForRange(range).join(this.getNewLineCharacter());
}
/**
* Returns all the text within `range` as an array of lines.
*/
getLinesForRange(range) {
let lines;
if (range.start.row === range.end.row) {
// Handle a single-line range.
lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)];
}
else {
// Handle a multi-line range.
lines = this.getLines(range.start.row, range.end.row);
lines[0] = (lines[0] || "").substring(range.start.column);
const l = lines.length - 1;
if (range.end.row - range.start.row === l) {
lines[l] = lines[l].substring(0, range.end.column);
}
}
return lines;
}
/**
* Inserts a block of `text` at the indicated `position`.
* Returns the end position of the inserted text, the character immediately after the last character inserted.
* This method also triggers the 'change' event.
*/
insert(position, text) {
// Only detect new lines if the document has no line break yet.
if (this.getLength() <= 1) {
this.#detect_new_line(text);
}
return this.insertMergedLines(position, $split(text));
}
/**
* Inserts `text` into the `position` at the current row. This method also triggers the `"change"` event.
*
* This differs from the `insert` method in two ways:
* 1. This does NOT handle newline characters (single-line text only).
* 2. This is faster than the `insert` method for single-line text insertions.
*/
insertInLine(position, text) {
const start = this.clippedPos(position.row, position.column);
const end = pos(position.row, position.column + text.length);
this.applyDelta({
start: start,
end: end,
action: "insert",
lines: [text]
});
return clonePos(end);
}
/**
* Clips the position so that it refers to the nearest valid position.
*/
clippedPos(row, column) {
const length = this.getLength();
let rowTooBig = false;
if (row === void 0) {
row = length;
}
else if (row < 0) {
row = 0;
}
else if (row >= length) {
row = length - 1;
rowTooBig = true;
}
const line = this.getLine(row);
if (rowTooBig) {
column = line.length;
}
column = Math.mi