Extract workspaceContents to own file

This commit is contained in:
Matt Bierner
2022-03-29 14:52:38 -07:00
parent 2783263582
commit 3ce5c78cb9
8 changed files with 175 additions and 145 deletions

View File

@@ -5,7 +5,7 @@
import { Command } from '../commandManager';
import { MarkdownEngine } from '../markdownEngine';
import { SkinnyTextDocument } from '../tableOfContentsProvider';
import { SkinnyTextDocument } from '../workspaceContents';
export class RenderDocument implements Command {
public readonly id = 'markdown.api.render';

View File

@@ -21,6 +21,7 @@ import { MarkdownPreviewManager } from './preview/previewManager';
import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './preview/security';
import { githubSlugifier } from './slugify';
import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter';
import { VsCodeMdWorkspaceContents } from './workspaceContents';
export function activate(context: vscode.ExtensionContext) {
@@ -57,13 +58,14 @@ function registerMarkdownLanguageFeatures(
const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' };
const linkProvider = new MdLinkProvider(engine);
const w = new VsCodeMdWorkspaceContents();
return vscode.Disposable.from(
vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider),
vscode.languages.registerDocumentLinkProvider(selector, linkProvider),
vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(engine)),
vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine)),
vscode.languages.registerWorkspaceSymbolProvider(new MdWorkspaceSymbolProvider(symbolProvider)),
vscode.languages.registerWorkspaceSymbolProvider(new MdWorkspaceSymbolProvider(symbolProvider, w)),
MdPathCompletionProvider.register(selector, engine, linkProvider),
);
}

View File

@@ -5,7 +5,8 @@
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { SkinnyTextDocument, TableOfContents, TocEntry } from '../tableOfContentsProvider';
import { TableOfContents, TocEntry } from '../tableOfContentsProvider';
import { SkinnyTextDocument } from '../workspaceContents';
interface MarkdownSymbol {
readonly level: number;

View File

@@ -4,129 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SkinnyTextDocument, SkinnyTextLine } from '../tableOfContentsProvider';
import { Disposable } from '../util/dispose';
import { isMarkdownFile } from '../util/file';
import { Lazy, lazy } from '../util/lazy';
import { SkinnyTextDocument, MdWorkspaceContents } from '../workspaceContents';
import { MdDocumentSymbolProvider } from './documentSymbolProvider';
export interface WorkspaceMarkdownDocumentProvider {
getAllMarkdownDocuments(): Thenable<Iterable<SkinnyTextDocument>>;
readonly onDidChangeMarkdownDocument: vscode.Event<SkinnyTextDocument>;
readonly onDidCreateMarkdownDocument: vscode.Event<SkinnyTextDocument>;
readonly onDidDeleteMarkdownDocument: vscode.Event<vscode.Uri>;
}
class VSCodeWorkspaceMarkdownDocumentProvider extends Disposable implements WorkspaceMarkdownDocumentProvider {
private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<SkinnyTextDocument>());
private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<SkinnyTextDocument>());
private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<vscode.Uri>());
private _watcher: vscode.FileSystemWatcher | undefined;
private readonly utf8Decoder = new TextDecoder('utf-8');
/**
* Reads and parses all .md documents in the workspace.
* Files are processed in batches, to keep the number of open files small.
*
* @returns Array of processed .md files.
*/
async getAllMarkdownDocuments(): Promise<SkinnyTextDocument[]> {
const maxConcurrent = 20;
const docList: SkinnyTextDocument[] = [];
const resources = await vscode.workspace.findFiles('**/*.md', '**/node_modules/**');
for (let i = 0; i < resources.length; i += maxConcurrent) {
const resourceBatch = resources.slice(i, i + maxConcurrent);
const documentBatch = (await Promise.all(resourceBatch.map(x => this.getMarkdownDocument(x)))).filter((doc) => !!doc) as SkinnyTextDocument[];
docList.push(...documentBatch);
}
return docList;
}
public get onDidChangeMarkdownDocument() {
this.ensureWatcher();
return this._onDidChangeMarkdownDocumentEmitter.event;
}
public get onDidCreateMarkdownDocument() {
this.ensureWatcher();
return this._onDidCreateMarkdownDocumentEmitter.event;
}
public get onDidDeleteMarkdownDocument() {
this.ensureWatcher();
return this._onDidDeleteMarkdownDocumentEmitter.event;
}
private ensureWatcher(): void {
if (this._watcher) {
return;
}
this._watcher = this._register(vscode.workspace.createFileSystemWatcher('**/*.md'));
this._register(this._watcher.onDidChange(async resource => {
const document = await this.getMarkdownDocument(resource);
if (document) {
this._onDidChangeMarkdownDocumentEmitter.fire(document);
}
}));
this._register(this._watcher.onDidCreate(async resource => {
const document = await this.getMarkdownDocument(resource);
if (document) {
this._onDidCreateMarkdownDocumentEmitter.fire(document);
}
}));
this._register(this._watcher.onDidDelete(resource => {
this._onDidDeleteMarkdownDocumentEmitter.fire(resource);
}));
this._register(vscode.workspace.onDidChangeTextDocument(e => {
if (isMarkdownFile(e.document)) {
this._onDidChangeMarkdownDocumentEmitter.fire(e.document);
}
}));
}
private async getMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
const matchingDocument = vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === resource.toString());
if (matchingDocument) {
return matchingDocument;
}
const bytes = await vscode.workspace.fs.readFile(resource);
// We assume that markdown is in UTF-8
const text = this.utf8Decoder.decode(bytes);
const lines: SkinnyTextLine[] = [];
const parts = text.split(/(\r?\n)/);
const lineCount = Math.floor(parts.length / 2) + 1;
for (let line = 0; line < lineCount; line++) {
lines.push({
text: parts[line * 2]
});
}
return {
uri: resource,
version: 0,
lineCount: lineCount,
lineAt: (index) => {
return lines[index];
},
getText: () => {
return text;
}
};
}
}
export class MdWorkspaceSymbolProvider extends Disposable implements vscode.WorkspaceSymbolProvider {
@@ -135,7 +17,7 @@ export class MdWorkspaceSymbolProvider extends Disposable implements vscode.Work
public constructor(
private _symbolProvider: MdDocumentSymbolProvider,
private _workspaceMarkdownDocumentProvider: WorkspaceMarkdownDocumentProvider = new VSCodeWorkspaceMarkdownDocumentProvider()
private _workspaceMarkdownDocumentProvider: MdWorkspaceContents,
) {
super();
}

View File

@@ -8,10 +8,10 @@ import Token = require('markdown-it/lib/token');
import * as vscode from 'vscode';
import { MarkdownContributionProvider } from './markdownExtensions';
import { Slugifier } from './slugify';
import { SkinnyTextDocument } from './tableOfContentsProvider';
import { stringHash } from './util/hash';
import { WebviewResourceProvider } from './util/resources';
import { isOfScheme, Schemes } from './util/schemes';
import { SkinnyTextDocument } from './workspaceContents';
const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;

View File

@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import { MarkdownEngine } from './markdownEngine';
import { githubSlugifier, Slug } from './slugify';
import { isMarkdownFile } from './util/file';
import { SkinnyTextDocument } from './workspaceContents';
export interface TocEntry {
readonly slug: Slug;
@@ -16,19 +17,6 @@ export interface TocEntry {
readonly location: vscode.Location;
}
export interface SkinnyTextLine {
text: string;
}
export interface SkinnyTextDocument {
readonly uri: vscode.Uri;
readonly version: number;
readonly lineCount: number;
lineAt(line: number): SkinnyTextLine;
getText(): string;
}
export class TableOfContents {
public static async create(engine: MarkdownEngine, document: SkinnyTextDocument,): Promise<TableOfContents> {

View File

@@ -7,7 +7,8 @@ import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbolProvider';
import { MdWorkspaceSymbolProvider, WorkspaceMarkdownDocumentProvider } from '../languageFeatures/workspaceSymbolProvider';
import { MdWorkspaceSymbolProvider } from '../languageFeatures/workspaceSymbolProvider';
import { MdWorkspaceContents } from '../workspaceContents';
import { createNewMarkdownEngine } from './engine';
import { InMemoryDocument } from './inMemoryDocument';
@@ -16,7 +17,7 @@ const symbolProvider = new MdDocumentSymbolProvider(createNewMarkdownEngine());
suite('markdown.WorkspaceSymbolProvider', () => {
test('Should not return anything for empty workspace', async () => {
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocumentProvider([]));
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments([]));
assert.deepStrictEqual(await provider.provideWorkspaceSymbols(''), []);
});
@@ -24,7 +25,7 @@ suite('markdown.WorkspaceSymbolProvider', () => {
test('Should return symbols from workspace with one markdown file', async () => {
const testFileName = vscode.Uri.file('test.md');
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocumentProvider([
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(testFileName, `# header1\nabc\n## header2`)
]));
@@ -42,7 +43,7 @@ suite('markdown.WorkspaceSymbolProvider', () => {
files.push(new InMemoryDocument(testFileName, `# common\nabc\n## header${i}`));
}
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocumentProvider(files));
const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments(files));
const symbols = await provider.provideWorkspaceSymbols('');
assert.strictEqual(symbols.length, fileNameCount * 2);
@@ -51,7 +52,7 @@ suite('markdown.WorkspaceSymbolProvider', () => {
test('Should update results when markdown file changes symbols', async () => {
const testFileName = vscode.Uri.file('test.md');
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocumentProvider([
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(testFileName, `# header1`, 1 /* version */)
]);
@@ -70,7 +71,7 @@ suite('markdown.WorkspaceSymbolProvider', () => {
test('Should remove results when file is deleted', async () => {
const testFileName = vscode.Uri.file('test.md');
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocumentProvider([
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(testFileName, `# header1`)
]);
@@ -86,7 +87,7 @@ suite('markdown.WorkspaceSymbolProvider', () => {
test('Should update results when markdown file is created', async () => {
const testFileName = vscode.Uri.file('test.md');
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocumentProvider([
const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([
new InMemoryDocument(testFileName, `# header1`)
]);
@@ -101,7 +102,7 @@ suite('markdown.WorkspaceSymbolProvider', () => {
});
class InMemoryWorkspaceMarkdownDocumentProvider implements WorkspaceMarkdownDocumentProvider {
class InMemoryWorkspaceMarkdownDocuments implements MdWorkspaceContents {
private readonly _documents = new Map<string, vscode.TextDocument>();
constructor(documents: vscode.TextDocument[]) {

View File

@@ -0,0 +1,156 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Disposable } from './util/dispose';
import { isMarkdownFile } from './util/file';
/**
* Minimal version of {@link vscode.TextLine}. Used for mocking out in testing.
*/
export interface SkinnyTextLine {
readonly text: string;
}
/**
* Minimal version of {@link vscode.TextDocument}. Used for mocking out in testing.
*/
export interface SkinnyTextDocument {
readonly uri: vscode.Uri;
readonly version: number;
readonly lineCount: number;
lineAt(line: number): SkinnyTextLine;
getText(): string;
}
/**
* Provides set of markdown files in the current workspace.
*/
export interface MdWorkspaceContents {
/**
* Get list of all known markdown files.
*/
getAllMarkdownDocuments(): Promise<Iterable<SkinnyTextDocument>>;
readonly onDidChangeMarkdownDocument: vscode.Event<SkinnyTextDocument>;
readonly onDidCreateMarkdownDocument: vscode.Event<SkinnyTextDocument>;
readonly onDidDeleteMarkdownDocument: vscode.Event<vscode.Uri>;
}
/**
* Provides set of markdown files known to VS Code.
*
* This includes both opened text documents and markdown files in the workspace.
*/
export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspaceContents {
private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<SkinnyTextDocument>());
private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<SkinnyTextDocument>());
private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<vscode.Uri>());
private _watcher: vscode.FileSystemWatcher | undefined;
private readonly utf8Decoder = new TextDecoder('utf-8');
/**
* Reads and parses all .md documents in the workspace.
* Files are processed in batches, to keep the number of open files small.
*
* @returns Array of processed .md files.
*/
async getAllMarkdownDocuments(): Promise<SkinnyTextDocument[]> {
const maxConcurrent = 20;
const docList: SkinnyTextDocument[] = [];
const resources = await vscode.workspace.findFiles('**/*.md', '**/node_modules/**');
for (let i = 0; i < resources.length; i += maxConcurrent) {
const resourceBatch = resources.slice(i, i + maxConcurrent);
const documentBatch = (await Promise.all(resourceBatch.map(x => this.getMarkdownDocument(x)))).filter((doc) => !!doc) as SkinnyTextDocument[];
docList.push(...documentBatch);
}
return docList;
}
public get onDidChangeMarkdownDocument() {
this.ensureWatcher();
return this._onDidChangeMarkdownDocumentEmitter.event;
}
public get onDidCreateMarkdownDocument() {
this.ensureWatcher();
return this._onDidCreateMarkdownDocumentEmitter.event;
}
public get onDidDeleteMarkdownDocument() {
this.ensureWatcher();
return this._onDidDeleteMarkdownDocumentEmitter.event;
}
private ensureWatcher(): void {
if (this._watcher) {
return;
}
this._watcher = this._register(vscode.workspace.createFileSystemWatcher('**/*.md'));
this._register(this._watcher.onDidChange(async resource => {
const document = await this.getMarkdownDocument(resource);
if (document) {
this._onDidChangeMarkdownDocumentEmitter.fire(document);
}
}));
this._register(this._watcher.onDidCreate(async resource => {
const document = await this.getMarkdownDocument(resource);
if (document) {
this._onDidCreateMarkdownDocumentEmitter.fire(document);
}
}));
this._register(this._watcher.onDidDelete(resource => {
this._onDidDeleteMarkdownDocumentEmitter.fire(resource);
}));
this._register(vscode.workspace.onDidChangeTextDocument(e => {
if (isMarkdownFile(e.document)) {
this._onDidChangeMarkdownDocumentEmitter.fire(e.document);
}
}));
}
private async getMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
const matchingDocument = vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === resource.toString());
if (matchingDocument) {
return matchingDocument;
}
const bytes = await vscode.workspace.fs.readFile(resource);
// We assume that markdown is in UTF-8
const text = this.utf8Decoder.decode(bytes);
const lines: SkinnyTextLine[] = [];
const parts = text.split(/(\r?\n)/);
const lineCount = Math.floor(parts.length / 2) + 1;
for (let line = 0; line < lineCount; line++) {
lines.push({
text: parts[line * 2]
});
}
return {
uri: resource,
version: 0,
lineCount: lineCount,
lineAt: (index) => {
return lines[index];
},
getText: () => {
return text;
}
};
}
}