Fix slow positionAt impl for markdown references

- Use `vscode-languageserver-textdocument` instead of custom impl
- Reuse `InMemoryDocument`  across tests and working code
- Use `SkinnyTextDocument` in more places
- Fixes some test errors that seem to be caused by bad `InMemoryDocument` impl
This commit is contained in:
Matt Bierner
2022-03-29 18:19:08 -07:00
parent 338ae07ccb
commit 8adb42079b
22 changed files with 141 additions and 188 deletions

View File

@@ -8,7 +8,7 @@ import 'mocha';
import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { joinLines, noopToken } from './util';

View File

@@ -8,7 +8,7 @@ import 'mocha';
import * as vscode from 'vscode';
import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbolProvider';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
const testFileName = vscode.Uri.file('test.md');

View File

@@ -7,7 +7,7 @@ import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
const testFileName = vscode.Uri.file('test.md');

View File

@@ -8,7 +8,7 @@ import 'mocha';
import * as vscode from 'vscode';
import { MdFoldingProvider } from '../languageFeatures/foldingProvider';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
const testFileName = vscode.Uri.file('test.md');
@@ -48,10 +48,10 @@ y`);
assert.strictEqual(folds.length, 2);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
assert.strictEqual(firstFold.end, 2);
});
test('Should collapse multuple newlines to single newline before next header', async () => {
test('Should collapse multiple newlines to single newline before next header', async () => {
const folds = await getFoldsForDocument(`
# a
x
@@ -63,7 +63,7 @@ y`);
assert.strictEqual(folds.length, 2);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 5);
assert.strictEqual(firstFold.end, 4);
});
test('Should not collapse if there is no newline before next header', async () => {
@@ -132,7 +132,7 @@ b`);
assert.strictEqual(folds.length, 1);
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 0);
assert.strictEqual(firstFold.end, 3);
assert.strictEqual(firstFold.end, 2);
});
test('Should fold fenced code blocks', async () => {

View File

@@ -1,78 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as vscode from 'vscode';
export class InMemoryDocument implements vscode.TextDocument {
private readonly _lines: string[];
constructor(
public readonly uri: vscode.Uri,
private readonly _contents: string,
public readonly version = 1,
) {
this._lines = this._contents.split(/\r\n|\n/g);
}
isUntitled: boolean = false;
languageId: string = '';
isDirty: boolean = false;
isClosed: boolean = false;
eol: vscode.EndOfLine = os.platform() === 'win32' ? vscode.EndOfLine.CRLF : vscode.EndOfLine.LF;
notebook: undefined;
get fileName(): string {
return this.uri.fsPath;
}
get lineCount(): number {
return this._lines.length;
}
lineAt(line: any): vscode.TextLine {
return {
lineNumber: line,
text: this._lines[line],
range: new vscode.Range(0, 0, 0, 0),
firstNonWhitespaceCharacterIndex: 0,
rangeIncludingLineBreak: new vscode.Range(0, 0, 0, 0),
isEmptyOrWhitespace: false
};
}
offsetAt(_position: vscode.Position): never {
throw new Error('Method not implemented.');
}
positionAt(offset: number): vscode.Position {
const before = this._contents.slice(0, offset);
const newLines = before.match(/\r\n|\n/g);
const line = newLines ? newLines.length : 0;
// eslint-disable-next-line code-no-look-behind-regex
const preCharacters = before.match(/(?<=\r\n|\n|^).*$/g);
return new vscode.Position(line, preCharacters ? preCharacters[0].length : 0);
}
getText(range?: vscode.Range): string {
if (!range) {
return this._contents;
}
if (range.start.line !== range.end.line) {
throw new Error('Method not implemented.');
}
return this._lines[range.start.line].slice(range.start.character, range.end.character);
}
getWordRangeAtPosition(_position: vscode.Position, _regex?: RegExp | undefined): never {
throw new Error('Method not implemented.');
}
validateRange(_range: vscode.Range): never {
throw new Error('Method not implemented.');
}
validatePosition(_position: vscode.Position): never {
throw new Error('Method not implemented.');
}
save(): never {
throw new Error('Method not implemented.');
}
}

View File

@@ -5,15 +5,15 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
import { MdWorkspaceContents } from '../workspaceContents';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
export class InMemoryWorkspaceMarkdownDocuments implements MdWorkspaceContents {
private readonly _documents = new Map<string, vscode.TextDocument>();
private readonly _documents = new Map<string, SkinnyTextDocument>();
constructor(documents: vscode.TextDocument[]) {
constructor(documents: SkinnyTextDocument[]) {
for (const doc of documents) {
this._documents.set(doc.fileName, doc);
this._documents.set(doc.uri.toString(), doc);
}
}
@@ -21,29 +21,29 @@ export class InMemoryWorkspaceMarkdownDocuments implements MdWorkspaceContents {
return Array.from(this._documents.values());
}
private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.TextDocument>();
private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter<SkinnyTextDocument>();
public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event;
private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.TextDocument>();
private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter<SkinnyTextDocument>();
public onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocumentEmitter.event;
private readonly _onDidDeleteMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.Uri>();
public onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocumentEmitter.event;
public updateDocument(document: vscode.TextDocument) {
this._documents.set(document.fileName, document);
public updateDocument(document: SkinnyTextDocument) {
this._documents.set(document.uri.toString(), document);
this._onDidChangeMarkdownDocumentEmitter.fire(document);
}
public createDocument(document: vscode.TextDocument) {
assert.ok(!this._documents.has(document.uri.fsPath));
public createDocument(document: SkinnyTextDocument) {
assert.ok(!this._documents.has(document.uri.toString()));
this._documents.set(document.uri.fsPath, document);
this._documents.set(document.uri.toString(), document);
this._onDidCreateMarkdownDocumentEmitter.fire(document);
}
public deleteDocument(resource: vscode.Uri) {
this._documents.delete(resource.fsPath);
this._documents.delete(resource.toString());
this._onDidDeleteMarkdownDocumentEmitter.fire(resource);
}
}

View File

@@ -9,7 +9,7 @@ import * as vscode from 'vscode';
import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdPathCompletionProvider } from '../languageFeatures/pathCompletions';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { CURSOR, getCursorPositions, joinLines, noopToken } from './util';

View File

@@ -10,7 +10,7 @@ import { MdLinkProvider } from '../languageFeatures/documentLinkProvider';
import { MdReferencesProvider } from '../languageFeatures/references';
import { MdWorkspaceContents } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
import { joinLines, noopToken, workspaceFile } from './util';
@@ -22,7 +22,7 @@ function getReferences(doc: InMemoryDocument, pos: vscode.Position, workspaceCon
return provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken);
}
suite.only('markdown header references', () => {
suite('markdown header references', () => {
test('Should not return references when not on header', async () => {
const doc = new InMemoryDocument(workspaceFile('doc.md'), joinLines(
`# abc`,

View File

@@ -7,7 +7,7 @@ import * as assert from 'assert';
import * as vscode from 'vscode';
import { MdSmartSelect } from '../languageFeatures/smartSelect';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { CURSOR, getCursorPositions, joinLines } from './util';
const testFileName = vscode.Uri.file('test.md');
@@ -197,34 +197,35 @@ suite('markdown.SmartSelect', () => {
test('Smart select fenced code block then list then subheader content then subheader then header content then header', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
`content 1`,
`## sub header 1`,
`- item 1`,
`- ~~~`,
` ${CURSOR}a`,
` ~~~`,
`- item 3`,
`- item 4`,
``,
`more content`,
`# main header 2`));
/* 00 */ `# main header 1`,
/* 01 */ `content 1`,
/* 02 */ `## sub header 1`,
/* 03 */ `- item 1`,
/* 04 */ `- ~~~`,
/* 05 */ ` ${CURSOR}a`,
/* 06 */ ` ~~~`,
/* 07 */ `- item 3`,
/* 08 */ `- item 4`,
/* 09 */ ``,
/* 10 */ `more content`,
/* 11 */ `# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [4, 6], [3, 9], [3, 10], [2, 10], [1, 10], [0, 10]);
assertNestedLineNumbersEqual(ranges![0], [4, 6], [3, 8], [3, 10], [2, 10], [1, 10], [0, 10]);
});
test('Smart select list with one element without selecting child subheader', async () => {
const ranges = await getSelectionRangesForDocument(
joinLines(
`# main header 1`,
``,
`- list ${CURSOR}`,
``,
`## sub header`,
``,
`content 2`,
`# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [1, 3], [1, 6], [0, 6]);
/* 00 */ `# main header 1`,
/* 01 */ ``,
/* 02 */ `- list ${CURSOR}`,
/* 03 */ ``,
/* 04 */ `## sub header`,
/* 05 */ ``,
/* 06 */ `content 2`,
/* 07 */ `# main header 2`));
assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 3], [1, 6], [0, 6]);
});
test('Smart select content under header then subheaders and their content', async () => {

View File

@@ -8,7 +8,7 @@ import 'mocha';
import * as vscode from 'vscode';
import { TableOfContents } from '../tableOfContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
const testFileName = vscode.Uri.file('test.md');

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as vscode from 'vscode';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
export const joinLines = (...args: string[]) =>
args.join(os.platform() === 'win32' ? '\r\n' : '\n');

View File

@@ -8,8 +8,9 @@ import 'mocha';
import * as vscode from 'vscode';
import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbolProvider';
import { MdWorkspaceSymbolProvider } from '../languageFeatures/workspaceSymbolProvider';
import { SkinnyTextDocument } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
@@ -37,7 +38,7 @@ suite('markdown.WorkspaceSymbolProvider', () => {
test('Should return all content basic workspace', async () => {
const fileNameCount = 10;
const files: vscode.TextDocument[] = [];
const files: SkinnyTextDocument[] = [];
for (let i = 0; i < fileNameCount; ++i) {
const testFileName = vscode.Uri.file(`test${i}.md`);
files.push(new InMemoryDocument(testFileName, `# common\nabc\n## header${i}`));