-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
544 additions
and
225 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export type { Position } from './position'; | ||
export type { Range } from './range'; | ||
export type { TextEdit } from './text-edit'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Range } from './range'; | ||
|
||
export interface TextEdit { | ||
range: Range; | ||
newText: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,139 +1,84 @@ | ||
import { Position } from '../common/position'; | ||
import { Range } from '../common/range'; | ||
import { Range } from '../common'; | ||
import { Document } from './document'; | ||
import { DocumentChange } from './document-factory'; | ||
import { ScriptureContainer } from './scripture-container'; | ||
import { ScriptureNode, ScriptureNodeType } from './scripture-node'; | ||
import { TextDocument } from './text-document'; | ||
|
||
export class ScriptureDocument extends ScriptureContainer implements Document { | ||
private _lineOffsets: number[] | undefined = undefined; | ||
private _content: string; | ||
private _version: number; | ||
export class ScriptureDocument extends TextDocument implements Document, ScriptureNode { | ||
private readonly _children: ScriptureNode[] = []; | ||
readonly parent: undefined = undefined; | ||
readonly isLeaf = false; | ||
readonly type = ScriptureNodeType.Document; | ||
readonly document = this; | ||
range: Range = { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }; | ||
|
||
constructor( | ||
public readonly uri: string, | ||
version: number, | ||
content: string, | ||
children?: ScriptureNode[], | ||
) { | ||
super(children); | ||
this._version = version; | ||
this._content = content; | ||
super(uri, version, content); | ||
if (children != null) { | ||
for (const child of children) { | ||
this.appendChild(child); | ||
} | ||
} | ||
} | ||
|
||
get type(): ScriptureNodeType { | ||
return ScriptureNodeType.Document; | ||
get children(): readonly ScriptureNode[] { | ||
return this._children; | ||
} | ||
|
||
get document(): this { | ||
return this; | ||
updateParent(_parent: ScriptureNode | undefined): void { | ||
throw new Error('The method is not supported.'); | ||
} | ||
|
||
get content(): string { | ||
return this._content; | ||
remove(): void { | ||
throw new Error('The method is not supported.'); | ||
} | ||
|
||
get version(): number { | ||
return this._version; | ||
*getNodes(filter?: ScriptureNodeType | ((node: ScriptureNode) => boolean)): IterableIterator<ScriptureNode> { | ||
for (const child of this._children) { | ||
if (filter == null || child.type === filter || (typeof filter === 'function' && filter(child))) { | ||
yield child; | ||
} | ||
yield* child.getNodes(filter); | ||
} | ||
} | ||
|
||
protected set version(value: number) { | ||
this._version = value; | ||
appendChild(child: ScriptureNode): void { | ||
this._children.push(child); | ||
child.updateParent(this); | ||
} | ||
|
||
getText(range?: Range): string { | ||
if (range != null) { | ||
const start = this.offsetAt(range.start); | ||
const end = this.offsetAt(range.end); | ||
return this._content.substring(start, end); | ||
} | ||
return this._content; | ||
insertChild(index: number, child: ScriptureNode): void { | ||
this._children.splice(index, 0, child); | ||
child.updateParent(this); | ||
} | ||
|
||
offsetAt(position: Position): number { | ||
const lineOffsets = this.getLineOffsets(); | ||
if (position.line >= lineOffsets.length) { | ||
return this._content.length; | ||
} else if (position.line < 0) { | ||
return 0; | ||
removeChild(child: ScriptureNode): void { | ||
if (child.parent !== this) { | ||
throw new Error('This node does not contain the specified child.'); | ||
} | ||
const lineOffset = lineOffsets[position.line]; | ||
if (position.character <= 0) { | ||
return lineOffset; | ||
const index = this._children.indexOf(child); | ||
if (index === -1) { | ||
throw new Error('This node does not contain the specified child.'); | ||
} | ||
|
||
const nextLineOffset = | ||
position.line + 1 < lineOffsets.length ? lineOffsets[position.line + 1] : this._content.length; | ||
const offset = Math.min(lineOffset + position.character, nextLineOffset); | ||
return this.ensureBeforeEndOfLine(offset, lineOffset); | ||
this._children.splice(index, 1); | ||
child.updateParent(undefined); | ||
} | ||
|
||
protected updateContent(change: DocumentChange): void { | ||
if (change.range == null) { | ||
this._content = change.text; | ||
this._lineOffsets = undefined; | ||
} else { | ||
const range = change.range; | ||
const startOffset = this.offsetAt(range.start); | ||
const endOffset = this.offsetAt(range.end); | ||
this._content = | ||
this._content.substring(0, startOffset) + | ||
change.text + | ||
this._content.substring(endOffset, this._content.length); | ||
|
||
// update the offsets | ||
const startLine = Math.max(range.start.line, 0); | ||
const endLine = Math.max(range.end.line, 0); | ||
let lineOffsets = this._lineOffsets!; | ||
const addedLineOffsets = computeLineOffsets(change.text, false, startOffset); | ||
if (endLine - startLine === addedLineOffsets.length) { | ||
for (let i = 0, len = addedLineOffsets.length; i < len; i++) { | ||
lineOffsets[i + startLine + 1] = addedLineOffsets[i]; | ||
} | ||
} else { | ||
if (addedLineOffsets.length < 10000) { | ||
lineOffsets.splice(startLine + 1, endLine - startLine, ...addedLineOffsets); | ||
} else { | ||
// avoid too many arguments for splice | ||
this._lineOffsets = lineOffsets = lineOffsets | ||
.slice(0, startLine + 1) | ||
.concat(addedLineOffsets, lineOffsets.slice(endLine + 1)); | ||
} | ||
} | ||
const diff = change.text.length - (endOffset - startOffset); | ||
if (diff !== 0) { | ||
for (let i = startLine + 1 + addedLineOffsets.length, len = lineOffsets.length; i < len; i++) { | ||
lineOffsets[i] = lineOffsets[i] + diff; | ||
} | ||
} | ||
spliceChildren(start: number, deleteCount: number, ...items: ScriptureNode[]): void { | ||
const removed = this._children.splice(start, deleteCount, ...items); | ||
for (const child of removed) { | ||
child.updateParent(undefined); | ||
} | ||
} | ||
|
||
public getLineOffsets(): number[] { | ||
if (this._lineOffsets === undefined) { | ||
this._lineOffsets = computeLineOffsets(this._content, true); | ||
} | ||
return this._lineOffsets; | ||
} | ||
|
||
public ensureBeforeEndOfLine(offset: number, lineOffset: number): number { | ||
while (offset > lineOffset && (this._content[offset - 1] === '\r' || this._content[offset - 1] === '\n')) { | ||
offset--; | ||
for (const child of items) { | ||
child.updateParent(this); | ||
} | ||
return offset; | ||
} | ||
} | ||
|
||
function computeLineOffsets(text: string, isAtLineStart: boolean, textOffset = 0): number[] { | ||
const result: number[] = isAtLineStart ? [textOffset] : []; | ||
for (let i = 0; i < text.length; i++) { | ||
const ch = text[i]; | ||
if (ch === '\n' || ch === '\r') { | ||
if (ch === '\r' && i + 1 < text.length && text[i + 1] === '\n') { | ||
i++; | ||
} | ||
result.push(textOffset + i + 1); | ||
} | ||
clearChildren(): void { | ||
this._children.length = 0; | ||
} | ||
return result; | ||
} |
Oops, something went wrong.