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

@@ -190,7 +190,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
) { }
public async provideDocumentLinks(
document: vscode.TextDocument,
document: SkinnyTextDocument,
_token: vscode.CancellationToken
): Promise<vscode.DocumentLink[]> {
const text = document.getText();
@@ -217,7 +217,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
return results;
}
public *getReferenceLinks(text: string, document: vscode.TextDocument): Iterable<vscode.DocumentLink> {
public *getReferenceLinks(text: string, document: SkinnyTextDocument): Iterable<vscode.DocumentLink> {
const definitions = this.getDefinitions(text, document);
for (const match of text.matchAll(referenceLinkPattern)) {
let linkStart: vscode.Position;
@@ -261,7 +261,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
}
}
public getDefinitions(text: string, document: vscode.TextDocument): Map<string, { readonly link: string; readonly linkRange: vscode.Range }> {
public getDefinitions(text: string, document: SkinnyTextDocument): Map<string, { readonly link: string; readonly linkRange: vscode.Range }> {
const out = new Map<string, { link: string; linkRange: vscode.Range }>();
for (const match of text.matchAll(definitionPattern)) {
const pre = match[1];

View File

@@ -7,6 +7,7 @@ import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { SkinnyTextDocument } from '../workspaceContents';
const rangeLimit = 5000;
@@ -21,7 +22,7 @@ export class MdFoldingProvider implements vscode.FoldingRangeProvider {
) { }
public async provideFoldingRanges(
document: vscode.TextDocument,
document: SkinnyTextDocument,
_: vscode.FoldingContext,
_token: vscode.CancellationToken
): Promise<vscode.FoldingRange[]> {
@@ -33,7 +34,7 @@ export class MdFoldingProvider implements vscode.FoldingRangeProvider {
return foldables.flat().slice(0, rangeLimit);
}
private async getRegions(document: vscode.TextDocument): Promise<vscode.FoldingRange[]> {
private async getRegions(document: SkinnyTextDocument): Promise<vscode.FoldingRange[]> {
const tokens = await this.engine.parse(document);
const regionMarkers = tokens.filter(isRegionMarker)
.map(token => ({ line: token.map[0], isStart: isStartRegion(token.content) }));
@@ -53,7 +54,7 @@ export class MdFoldingProvider implements vscode.FoldingRangeProvider {
.filter((region: vscode.FoldingRange | null): region is vscode.FoldingRange => !!region);
}
private async getHeaderFoldingRanges(document: vscode.TextDocument) {
private async getHeaderFoldingRanges(document: SkinnyTextDocument) {
const toc = await TableOfContents.create(this.engine, document);
return toc.entries.map(entry => {
let endLine = entry.location.range.end.line;
@@ -64,7 +65,7 @@ export class MdFoldingProvider implements vscode.FoldingRangeProvider {
});
}
private async getBlockFoldingRanges(document: vscode.TextDocument): Promise<vscode.FoldingRange[]> {
private async getBlockFoldingRanges(document: SkinnyTextDocument): Promise<vscode.FoldingRange[]> {
const tokens = await this.engine.parse(document);
const multiLineListItems = tokens.filter(isFoldableToken);
return multiLineListItems.map(listItem => {

View File

@@ -8,6 +8,7 @@ import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { resolveUriToMarkdownFile } from '../util/openDocumentLink';
import { SkinnyTextDocument } from '../workspaceContents';
import { MdLinkProvider } from './documentLinkProvider';
enum CompletionContextKind {
@@ -90,7 +91,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
private readonly linkProvider: MdLinkProvider,
) { }
public async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken, _context: vscode.CompletionContext): Promise<vscode.CompletionItem[]> {
public async provideCompletionItems(document: SkinnyTextDocument, position: vscode.Position, _token: vscode.CancellationToken, _context: vscode.CompletionContext): Promise<vscode.CompletionItem[]> {
if (!this.arePathSuggestionEnabled(document)) {
return [];
}
@@ -144,7 +145,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
}
}
private arePathSuggestionEnabled(document: vscode.TextDocument): boolean {
private arePathSuggestionEnabled(document: SkinnyTextDocument): boolean {
const config = vscode.workspace.getConfiguration('markdown', document.uri);
return config.get('suggest.paths.enabled', true);
}
@@ -158,7 +159,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
/// [id]: |
private readonly definitionPattern = /^\s*\[[\w\-]+\]:\s*([^\s]*)$/m;
private getPathCompletionContext(document: vscode.TextDocument, position: vscode.Position): CompletionContext | undefined {
private getPathCompletionContext(document: SkinnyTextDocument, position: vscode.Position): CompletionContext | undefined {
const line = document.lineAt(position.line).text;
const linePrefixText = line.slice(0, position.character);
@@ -231,7 +232,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
};
}
private *provideReferenceSuggestions(document: vscode.TextDocument, position: vscode.Position, context: CompletionContext): Iterable<vscode.CompletionItem> {
private *provideReferenceSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext): Iterable<vscode.CompletionItem> {
const insertionRange = new vscode.Range(context.linkTextStartPosition, position);
const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length }));
@@ -248,7 +249,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
}
}
private async *provideHeaderSuggestions(document: vscode.TextDocument, position: vscode.Position, context: CompletionContext, insertionRange: vscode.Range): AsyncIterable<vscode.CompletionItem> {
private async *provideHeaderSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext, insertionRange: vscode.Range): AsyncIterable<vscode.CompletionItem> {
const toc = await TableOfContents.createForDocumentOrNotebook(this.engine, document);
for (const entry of toc.entries) {
const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length }));
@@ -263,7 +264,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
}
}
private async *providePathSuggestions(document: vscode.TextDocument, position: vscode.Position, context: CompletionContext): AsyncIterable<vscode.CompletionItem> {
private async *providePathSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext): AsyncIterable<vscode.CompletionItem> {
const valueBeforeLastSlash = context.linkPrefix.substring(0, context.linkPrefix.lastIndexOf('/') + 1); // keep the last slash
const parentDir = this.resolveReference(document, valueBeforeLastSlash || '.');
@@ -304,7 +305,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
}
}
private resolveReference(document: vscode.TextDocument, ref: string): vscode.Uri | undefined {
private resolveReference(document: SkinnyTextDocument, ref: string): vscode.Uri | undefined {
const docUri = this.getFileUriOfTextDocument(document);
if (ref.startsWith('/')) {
@@ -333,7 +334,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
}
}
private getFileUriOfTextDocument(document: vscode.TextDocument) {
private getFileUriOfTextDocument(document: SkinnyTextDocument) {
if (document.uri.scheme === 'vscode-notebook-cell') {
const notebook = vscode.workspace.notebookDocuments
.find(notebook => notebook.getCells().some(cell => cell.document === document));

View File

@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { Disposable } from '../util/dispose';
import { MdWorkspaceContents } from '../workspaceContents';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { InternalLinkTarget, LinkData, MdLinkProvider } from './documentLinkProvider';
import { MdWorkspaceCache } from './workspaceCache';
@@ -26,7 +26,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference
this._linkCache = this._register(new MdWorkspaceCache(workspaceContents, doc => linkProvider.getInlineLinks(doc.getText(), doc)));
}
async provideReferences(document: vscode.TextDocument, position: vscode.Position, _context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[] | undefined> {
async provideReferences(document: SkinnyTextDocument, position: vscode.Position, _context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[] | undefined> {
const toc = await TableOfContents.create(this.engine, document);
if (token.isCancellationRequested) {
return undefined;

View File

@@ -6,6 +6,7 @@ import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents, TocEntry } from '../tableOfContents';
import { SkinnyTextDocument } from '../workspaceContents';
interface MarkdownItTokenWithMap extends Token {
map: [number, number];
@@ -17,24 +18,24 @@ export class MdSmartSelect implements vscode.SelectionRangeProvider {
private readonly engine: MarkdownEngine
) { }
public async provideSelectionRanges(document: vscode.TextDocument, positions: vscode.Position[], _token: vscode.CancellationToken): Promise<vscode.SelectionRange[] | undefined> {
public async provideSelectionRanges(document: SkinnyTextDocument, positions: vscode.Position[], _token: vscode.CancellationToken): Promise<vscode.SelectionRange[] | undefined> {
const promises = await Promise.all(positions.map((position) => {
return this.provideSelectionRange(document, position, _token);
}));
return promises.filter(item => item !== undefined) as vscode.SelectionRange[];
}
private async provideSelectionRange(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.SelectionRange | undefined> {
private async provideSelectionRange(document: SkinnyTextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.SelectionRange | undefined> {
const headerRange = await this.getHeaderSelectionRange(document, position);
const blockRange = await this.getBlockSelectionRange(document, position, headerRange);
const inlineRange = await this.getInlineSelectionRange(document, position, blockRange);
return inlineRange || blockRange || headerRange;
}
private async getInlineSelectionRange(document: vscode.TextDocument, position: vscode.Position, blockRange?: vscode.SelectionRange): Promise<vscode.SelectionRange | undefined> {
private async getInlineSelectionRange(document: SkinnyTextDocument, position: vscode.Position, blockRange?: vscode.SelectionRange): Promise<vscode.SelectionRange | undefined> {
return createInlineRange(document, position, blockRange);
}
private async getBlockSelectionRange(document: vscode.TextDocument, position: vscode.Position, headerRange?: vscode.SelectionRange): Promise<vscode.SelectionRange | undefined> {
private async getBlockSelectionRange(document: SkinnyTextDocument, position: vscode.Position, headerRange?: vscode.SelectionRange): Promise<vscode.SelectionRange | undefined> {
const tokens = await this.engine.parse(document);
@@ -52,7 +53,7 @@ export class MdSmartSelect implements vscode.SelectionRangeProvider {
return currentRange;
}
private async getHeaderSelectionRange(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.SelectionRange | undefined> {
private async getHeaderSelectionRange(document: SkinnyTextDocument, position: vscode.Position): Promise<vscode.SelectionRange | undefined> {
const toc = await TableOfContents.create(this.engine, document);
const headerInfo = getHeadersForPosition(toc.entries, position);
@@ -107,7 +108,7 @@ function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, p
return sortedTokens;
}
function createBlockRange(block: MarkdownItTokenWithMap, document: vscode.TextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
function createBlockRange(block: MarkdownItTokenWithMap, document: SkinnyTextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
if (block.type === 'fence') {
return createFencedRange(block, cursorLine, document, parent);
} else {
@@ -129,7 +130,7 @@ function createBlockRange(block: MarkdownItTokenWithMap, document: vscode.TextDo
}
}
function createInlineRange(document: vscode.TextDocument, cursorPosition: vscode.Position, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
function createInlineRange(document: SkinnyTextDocument, cursorPosition: vscode.Position, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
const lineText = document.lineAt(cursorPosition.line).text;
const boldSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, parent);
const italicSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, parent);
@@ -146,7 +147,7 @@ function createInlineRange(document: vscode.TextDocument, cursorPosition: vscode
return inlineCodeBlockSelection || linkSelection || comboSelection || boldSelection || italicSelection;
}
function createFencedRange(token: MarkdownItTokenWithMap, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
function createFencedRange(token: MarkdownItTokenWithMap, cursorLine: number, document: SkinnyTextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
const startLine = token.map[0];
const endLine = token.map[1] - 1;
const onFenceLine = cursorLine === startLine || cursorLine === endLine;
@@ -236,7 +237,7 @@ function isBlockElement(token: Token): boolean {
return !['list_item_close', 'paragraph_close', 'bullet_list_close', 'inline', 'heading_close', 'heading_open'].includes(token.type);
}
function getFirstChildHeader(document: vscode.TextDocument, header?: TocEntry, toc?: readonly TocEntry[]): vscode.Position | undefined {
function getFirstChildHeader(document: SkinnyTextDocument, header?: TocEntry, toc?: readonly TocEntry[]): vscode.Position | undefined {
let childRange: vscode.Position | undefined;
if (header && toc) {
let children = toc.filter(t => header.location.range.contains(t.location.range) && t.location.range.start.line > header.location.range.start.line).sort((t1, t2) => t1.line - t2.line);

View File

@@ -39,16 +39,16 @@ export class MdWorkspaceCache<T> extends Disposable {
private async populateSymbolCache(): Promise<void> {
const markdownDocumentUris = await this.workspaceContents.getAllMarkdownDocuments();
for (const document of markdownDocumentUris) {
this._cache.set(document.uri.toString(), this.update(document));
this.update(document);
}
}
private update(document: SkinnyTextDocument): Lazy<T> {
return lazy(() => this.getValue(document));
private update(document: SkinnyTextDocument): void {
this._cache.set(document.uri.toString(), lazy(() => this.getValue(document)));
}
private onDidChangeDocument(document: SkinnyTextDocument) {
this._cache.set(document.uri.toString(), this.update(document));
this.update(document);
}
private onDidDeleteDocument(resource: vscode.Uri) {