Skip to content

Instantly share code, notes, and snippets.

@romgrk
Created October 16, 2016 01:34
Show Gist options
  • Save romgrk/7b155dcc273d46f125c22fe5de2b2c4f to your computer and use it in GitHub Desktop.
Save romgrk/7b155dcc273d46f125c22fe5de2b2c4f to your computer and use it in GitHub Desktop.
import {
inspect,
createToken, Token} from '../common';
let stringConcat = (prev: string, cur: string) => prev.concat(cur)
function tokensToString (tokens: Token[]) {
return tokens.map(t => t.text)
.reduce(stringConcat, '');
}
function clone(object) {
return JSON.parse(JSON.stringify(object));
}
export default class LineModel {
tokens: Token[];
changed: () => void;
private _size: number;
constructor(size: number) {
this._size = size;
this.tokens = [];
this.changed = () => {};
}
get content() {
let res = tokensToString(this.tokens);
if (!this._size)
return res;
if (res.length < this._size)
res += " ".repeat(this._size - res.length);
if (res.length > this._size)
res = res.substring(0, this._size);
return res;
}
set content(value: string) {
this.tokens = [{text: value}];
}
public insert(pos: number, data: any) {
const newToken = createToken(data);
const n_tokens = this.tokens.length;
let start_index: number,
start_offset: number,
end_index: number,
end_offset: number;
let charOffset = 0;
for (let n = 0; n < n_tokens; n++) {
let token = this.tokens[n];
let len = token.text.length;
charOffset += len;
if (start_index == undefined && charOffset >= pos) {
start_index = n;
start_offset = pos - (charOffset - len);
}
if (charOffset >= pos + newToken.text.length) {
end_index = n;
end_offset = (pos + newToken.text.length) - (charOffset - len);
break;
}
}
// inspect({pos, charOffset, start_index, start_offset, end_index, end_offset})
if (start_index == undefined) {
if (charOffset < pos) {
this.tokens.push(createToken(pos - charOffset));
}
start_index = this.tokens.length;
end_index = this.tokens.length;
} else if (end_index == undefined) {
start_index = this.splitAt(start_index, start_offset);
end_index = this.tokens.length;
} else if (start_index == end_index) {
end_offset -= start_offset;
start_index = this.splitAt(start_index, start_offset);
end_index = this.splitAt(start_index, end_offset);
} else {
start_index = this.splitAt(start_index, start_offset);
if (n_tokens != this.tokens.length)
end_index += 1;
end_index = this.splitAt(end_index, end_offset);
}
let delete_count = end_index - start_index;
// inspect({start_index, delete_count}, this.tokens)
this.tokens.splice(start_index, delete_count, newToken);
}
public prepend(...items: Token[]) {
for (let item of items) {
this.tokens.unshift(createToken(item));
}
}
public append(...items: Token[]) {
for (let item of items) {
this.tokens.push(createToken(item));
}
}
public slice(start: number, end?: number) {
const startIndex = this.splitNodeAt(start);
const endIndex = (end != undefined) ? this.splitNodeAt(end) : this.tokens.length;
return this.tokens
.slice(startIndex, endIndex)
.map(t => clone(t));
}
/**
* Remove a range tokens.
* see Array.prototype.slice
* @param pos:number the zero-based character index
* @param count:number [defaults to line size]
* @param ...items:[] [replacement items]
* @returns the extracted tokens
*/
public splice(pos: number, count = this._size - pos, ...items: Token[]) {
const startIndex = this.splitNodeAt(pos);
const endIndex = this.splitNodeAt(pos + count);
return this.tokens.splice(
startIndex,
endIndex - startIndex,
...items
);
}
/**
* Replace tokens in range with an empty token.
* @param pos:number the zero-based character index
* @param count:number [defaults to line size]
* @returns the extracted tokens
*/
public extract(pos: number, count = this._size - pos, items?: Token[]) {
const startIndex = this.splitNodeAt(pos);
const endIndex = this.splitNodeAt(pos + count);
if (items == undefined)
items = [createToken(count)];
return this.tokens.splice(
startIndex,
endIndex - startIndex,
...items
);
}
/**
* Asserts that the node at {@param offset} ends/begins there.
* @returns the index of the first token after split
*/
private splitNodeAt(offset: number): number {
if (offset == 0)
return 0;
const n_tokens = this.tokens.length;
let charOffset = 0,
index: number,
innerOffset: number;
for (let n = 0; n < n_tokens; n++) {
let token = this.tokens[n];
let len = token.text.length;
charOffset += len;
if (charOffset >= offset) {
index = n;
innerOffset = offset - (charOffset - len);
break;
}
}
if (index == undefined)
return n_tokens;
return this.splitAt(index, innerOffset);
}
/**
* Asserts that the node @param index is split at @param offset.
* @returns the index of the first token after split
*/
private splitAt(index: number, offset: number) {
if (index > this.tokens.length)
return this.tokens.length;
if (offset == 0)
return index;
let token = this.tokens[index];
let text = token.text;
if (offset >= text.length)
return index + 1;
this.tokens.splice(index, 0, clone(token));
this.tokens[index].text = text.substring(0, offset);
this.tokens[index + 1].text = text.substring(offset);
return index + 1;
}
/*
* Section: Boring getter/setters
*/
public setSize(value: number) {
if (value > 0)
this._size = value;
}
public getSize() {
return this._size;
}
public toString(): string {
return tokensToString(this.tokens);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment