// ***************************************************************************** // Copyright (C) 2018 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { TextEditorDocument } from '@theia/editor/lib/browser'; export interface ContentLines extends ArrayLike { readonly length: number, getLineContent: (line: number) => string, } export interface ContentLinesArrayLike extends ContentLines, ArrayLike { [Symbol.iterator]: () => IterableIterator, readonly [n: number]: string; } export namespace ContentLines { const NL = '\n'.charCodeAt(0); const CR = '\r'.charCodeAt(0); export function fromString(content: string): ContentLines { const computeLineStarts: (s: string) => number[] = s => { const result: number[] = [0]; for (let i = 0; i < s.length; i++) { const chr = s.charCodeAt(i); if (chr === CR) { if (i + 1 < s.length && s.charCodeAt(i + 1) === NL) { result[result.length] = i + 2; i++; } else { result[result.length] = i + 1; } } else if (chr === NL) { result[result.length] = i + 1; } } return result; }; const lineStarts = computeLineStarts(content); return { length: lineStarts.length, getLineContent: line => { if (line >= lineStarts.length) { throw new Error('line index out of bounds'); } const start = lineStarts[line]; let end = (line === lineStarts.length - 1) ? undefined : lineStarts[line + 1] - 1; if (!!end && content.charCodeAt(end - 1) === CR) { end--; // ignore CR at the end } const lineContent = content.substring(start, end); return lineContent; } }; } export function fromTextEditorDocument(document: TextEditorDocument): ContentLines { return { length: document.lineCount, getLineContent: line => document.getLineContent(line + 1), }; } export function arrayLike(lines: ContentLines): ContentLinesArrayLike { return new Proxy(lines as ContentLines, getProxyHandler()) as ContentLinesArrayLike; } function getProxyHandler(): ProxyHandler { return { // eslint-disable-next-line @typescript-eslint/no-explicit-any get(target: ContentLines, p: PropertyKey): any { switch (p) { case 'prototype': return undefined; case 'length': return target.length; case 'slice': return (start?: number, end?: number) => { if (start !== undefined) { return [start, (end !== undefined ? end - 1 : target.length - 1)]; } return [0, target.length - 1]; }; case Symbol.iterator: return function* (): IterableIterator { for (let i = 0; i < target.length; i++) { yield target.getLineContent(i); } }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const index = Number.parseInt(p as any); if (Number.isInteger(index)) { if (index >= 0 && index < target.length) { const value = target.getLineContent(index); if (value === undefined) { console.log(target); } return value; } else { return undefined; } } throw new Error(`get ${String(p)} not implemented`); } }; } }