mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-02 06:21:50 +01:00
Experiment with switching markdown extension to use native privates
Let's try this out with one extension to start
This commit is contained in:
@@ -17,37 +17,43 @@ export type LanguageClientConstructor = (name: string, description: string, clie
|
||||
|
||||
export class MdLanguageClient implements IDisposable {
|
||||
|
||||
readonly #client: BaseLanguageClient;
|
||||
readonly #workspace: VsCodeMdWorkspace;
|
||||
|
||||
constructor(
|
||||
private readonly _client: BaseLanguageClient,
|
||||
private readonly _workspace: VsCodeMdWorkspace,
|
||||
) { }
|
||||
client: BaseLanguageClient,
|
||||
workspace: VsCodeMdWorkspace,
|
||||
) {
|
||||
this.#client = client;
|
||||
this.#workspace = workspace;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._client.stop();
|
||||
this._workspace.dispose();
|
||||
this.#client.stop();
|
||||
this.#workspace.dispose();
|
||||
}
|
||||
|
||||
resolveLinkTarget(linkText: string, uri: vscode.Uri): Promise<proto.ResolvedDocumentLinkTarget> {
|
||||
return this._client.sendRequest(proto.resolveLinkTarget, { linkText, uri: uri.toString() });
|
||||
return this.#client.sendRequest(proto.resolveLinkTarget, { linkText, uri: uri.toString() });
|
||||
}
|
||||
|
||||
getEditForFileRenames(files: ReadonlyArray<{ oldUri: string; newUri: string }>, token: vscode.CancellationToken) {
|
||||
return this._client.sendRequest(proto.getEditForFileRenames, files, token);
|
||||
return this.#client.sendRequest(proto.getEditForFileRenames, files, token);
|
||||
}
|
||||
|
||||
getReferencesToFileInWorkspace(resource: vscode.Uri, token: vscode.CancellationToken) {
|
||||
return this._client.sendRequest(proto.getReferencesToFileInWorkspace, { uri: resource.toString() }, token);
|
||||
return this.#client.sendRequest(proto.getReferencesToFileInWorkspace, { uri: resource.toString() }, token);
|
||||
}
|
||||
|
||||
prepareUpdatePastedLinks(doc: vscode.Uri, ranges: readonly vscode.Range[], token: vscode.CancellationToken) {
|
||||
return this._client.sendRequest(proto.prepareUpdatePastedLinks, {
|
||||
return this.#client.sendRequest(proto.prepareUpdatePastedLinks, {
|
||||
uri: doc.toString(),
|
||||
ranges: ranges.map(range => Range.create(range.start.line, range.start.character, range.end.line, range.end.character)),
|
||||
}, token);
|
||||
}
|
||||
|
||||
getUpdatePastedLinksEdit(pastingIntoDoc: vscode.Uri, edits: readonly vscode.TextEdit[], metadata: string, token: vscode.CancellationToken) {
|
||||
return this._client.sendRequest(proto.getUpdatePastedLinksEdit, {
|
||||
return this.#client.sendRequest(proto.getUpdatePastedLinksEdit, {
|
||||
metadata,
|
||||
pasteIntoDoc: pastingIntoDoc.toString(),
|
||||
edits: edits.map(edit => TextEdit.replace(edit.range, edit.newText)),
|
||||
|
||||
@@ -17,12 +17,12 @@ type DirWatcherEntry = {
|
||||
|
||||
export class FileWatcherManager {
|
||||
|
||||
private readonly _fileWatchers = new Map<number, {
|
||||
readonly #fileWatchers = new Map<number, {
|
||||
readonly watcher: vscode.FileSystemWatcher;
|
||||
readonly dirWatchers: DirWatcherEntry[];
|
||||
}>();
|
||||
|
||||
private readonly _dirWatchers = new ResourceMap<{
|
||||
readonly #dirWatchers = new ResourceMap<{
|
||||
readonly watcher: vscode.FileSystemWatcher;
|
||||
refCount: number;
|
||||
}>();
|
||||
@@ -35,7 +35,7 @@ export class FileWatcherManager {
|
||||
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'), !listeners.create, !listeners.change, !listeners.delete);
|
||||
const parentDirWatchers: DirWatcherEntry[] = [];
|
||||
this._fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers });
|
||||
this.#fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers });
|
||||
|
||||
if (listeners.create) { watcher.onDidCreate(listeners.create); }
|
||||
if (listeners.change) { watcher.onDidChange(listeners.change); }
|
||||
@@ -46,12 +46,12 @@ export class FileWatcherManager {
|
||||
for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) {
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
let parentDirWatcher = this._dirWatchers.get(dirUri);
|
||||
let parentDirWatcher = this.#dirWatchers.get(dirUri);
|
||||
if (!parentDirWatcher) {
|
||||
const glob = new vscode.RelativePattern(Utils.dirname(dirUri), Utils.basename(dirUri));
|
||||
const parentWatcher = vscode.workspace.createFileSystemWatcher(glob, !listeners.create, true, !listeners.delete);
|
||||
parentDirWatcher = { refCount: 0, watcher: parentWatcher };
|
||||
this._dirWatchers.set(dirUri, parentDirWatcher);
|
||||
this.#dirWatchers.set(dirUri, parentDirWatcher);
|
||||
}
|
||||
parentDirWatcher.refCount++;
|
||||
|
||||
@@ -81,16 +81,16 @@ export class FileWatcherManager {
|
||||
}
|
||||
|
||||
delete(id: number): void {
|
||||
const entry = this._fileWatchers.get(id);
|
||||
const entry = this.#fileWatchers.get(id);
|
||||
if (entry) {
|
||||
for (const dirWatcher of entry.dirWatchers) {
|
||||
disposeAll(dirWatcher.disposables);
|
||||
|
||||
const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri);
|
||||
const dirWatcherEntry = this.#dirWatchers.get(dirWatcher.uri);
|
||||
if (dirWatcherEntry) {
|
||||
if (--dirWatcherEntry.refCount <= 0) {
|
||||
dirWatcherEntry.watcher.dispose();
|
||||
this._dirWatchers.delete(dirWatcher.uri);
|
||||
this.#dirWatchers.delete(dirWatcher.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,6 @@ export class FileWatcherManager {
|
||||
entry.watcher.dispose();
|
||||
}
|
||||
|
||||
this._fileWatchers.delete(id);
|
||||
this.#fileWatchers.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ITextDocument } from '../types/textDocument';
|
||||
|
||||
export class InMemoryDocument implements ITextDocument {
|
||||
|
||||
private readonly _doc: TextDocument;
|
||||
readonly #doc: TextDocument;
|
||||
|
||||
public readonly uri: vscode.Uri;
|
||||
public readonly version: number;
|
||||
@@ -21,15 +21,15 @@ export class InMemoryDocument implements ITextDocument {
|
||||
) {
|
||||
this.uri = uri;
|
||||
this.version = version;
|
||||
this._doc = TextDocument.create(this.uri.toString(), 'markdown', 0, contents);
|
||||
this.#doc = TextDocument.create(this.uri.toString(), 'markdown', 0, contents);
|
||||
}
|
||||
|
||||
getText(range?: vscode.Range): string {
|
||||
return this._doc.getText(range);
|
||||
return this.#doc.getText(range);
|
||||
}
|
||||
|
||||
positionAt(offset: number): vscode.Position {
|
||||
const pos = this._doc.positionAt(offset);
|
||||
const pos = this.#doc.positionAt(offset);
|
||||
return new vscode.Position(pos.line, pos.character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,47 +17,47 @@ import { ResourceMap } from '../util/resourceMap';
|
||||
*/
|
||||
export class VsCodeMdWorkspace extends Disposable {
|
||||
|
||||
private readonly _watcher: vscode.FileSystemWatcher | undefined;
|
||||
readonly #watcher: vscode.FileSystemWatcher | undefined;
|
||||
|
||||
private readonly _documentCache = new ResourceMap<ITextDocument>();
|
||||
readonly #documentCache = new ResourceMap<ITextDocument>();
|
||||
|
||||
private readonly _utf8Decoder = new TextDecoder('utf-8');
|
||||
readonly #utf8Decoder = new TextDecoder('utf-8');
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._watcher = this._register(vscode.workspace.createFileSystemWatcher('**/*.md'));
|
||||
this.#watcher = this._register(vscode.workspace.createFileSystemWatcher('**/*.md'));
|
||||
|
||||
this._register(this._watcher.onDidChange(async resource => {
|
||||
this._documentCache.delete(resource);
|
||||
this._register(this.#watcher.onDidChange(async resource => {
|
||||
this.#documentCache.delete(resource);
|
||||
}));
|
||||
|
||||
this._register(this._watcher.onDidDelete(resource => {
|
||||
this._documentCache.delete(resource);
|
||||
this._register(this.#watcher.onDidDelete(resource => {
|
||||
this.#documentCache.delete(resource);
|
||||
}));
|
||||
|
||||
this._register(vscode.workspace.onDidOpenTextDocument(e => {
|
||||
this._documentCache.delete(e.uri);
|
||||
this.#documentCache.delete(e.uri);
|
||||
}));
|
||||
|
||||
this._register(vscode.workspace.onDidCloseTextDocument(e => {
|
||||
this._documentCache.delete(e.uri);
|
||||
this.#documentCache.delete(e.uri);
|
||||
}));
|
||||
}
|
||||
|
||||
private _isRelevantMarkdownDocument(doc: vscode.TextDocument) {
|
||||
#isRelevantMarkdownDocument(doc: vscode.TextDocument) {
|
||||
return isMarkdownFile(doc) && doc.uri.scheme !== 'vscode-bulkeditpreview';
|
||||
}
|
||||
|
||||
public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise<ITextDocument | undefined> {
|
||||
const existing = this._documentCache.get(resource);
|
||||
const existing = this.#documentCache.get(resource);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const matchingDocument = vscode.workspace.textDocuments.find((doc) => this._isRelevantMarkdownDocument(doc) && doc.uri.toString() === resource.toString());
|
||||
const matchingDocument = vscode.workspace.textDocuments.find((doc) => this.#isRelevantMarkdownDocument(doc) && doc.uri.toString() === resource.toString());
|
||||
if (matchingDocument) {
|
||||
this._documentCache.set(resource, matchingDocument);
|
||||
this.#documentCache.set(resource, matchingDocument);
|
||||
return matchingDocument;
|
||||
}
|
||||
|
||||
@@ -69,9 +69,9 @@ export class VsCodeMdWorkspace extends Disposable {
|
||||
const bytes = await vscode.workspace.fs.readFile(resource);
|
||||
|
||||
// We assume that markdown is in UTF-8
|
||||
const text = this._utf8Decoder.decode(bytes);
|
||||
const text = this.#utf8Decoder.decode(bytes);
|
||||
const doc = new InMemoryDocument(resource, text, 0);
|
||||
this._documentCache.set(resource, doc);
|
||||
this.#documentCache.set(resource, doc);
|
||||
return doc;
|
||||
} catch {
|
||||
return undefined;
|
||||
|
||||
@@ -12,27 +12,27 @@ export interface Command {
|
||||
}
|
||||
|
||||
export class CommandManager {
|
||||
private readonly _commands = new Map<string, vscode.Disposable>();
|
||||
readonly #commands = new Map<string, vscode.Disposable>();
|
||||
|
||||
public dispose() {
|
||||
for (const registration of this._commands.values()) {
|
||||
for (const registration of this.#commands.values()) {
|
||||
registration.dispose();
|
||||
}
|
||||
this._commands.clear();
|
||||
this.#commands.clear();
|
||||
}
|
||||
|
||||
public register<T extends Command>(command: T): vscode.Disposable {
|
||||
this._registerCommand(command.id, command.execute, command);
|
||||
this.#registerCommand(command.id, command.execute, command);
|
||||
return new vscode.Disposable(() => {
|
||||
this._commands.delete(command.id);
|
||||
this.#commands.delete(command.id);
|
||||
});
|
||||
}
|
||||
|
||||
private _registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any) {
|
||||
if (this._commands.has(id)) {
|
||||
#registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any) {
|
||||
if (this.#commands.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._commands.set(id, vscode.commands.registerCommand(id, impl, thisArg));
|
||||
this.#commands.set(id, vscode.commands.registerCommand(id, impl, thisArg));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ import { MarkdownPreviewManager } from '../preview/previewManager';
|
||||
export class CopyImageCommand implements Command {
|
||||
public readonly id = '_markdown.copyImage';
|
||||
|
||||
readonly #webviewManager: MarkdownPreviewManager;
|
||||
|
||||
public constructor(
|
||||
private readonly _webviewManager: MarkdownPreviewManager,
|
||||
) { }
|
||||
webviewManager: MarkdownPreviewManager,
|
||||
) {
|
||||
this.#webviewManager = webviewManager;
|
||||
}
|
||||
|
||||
public execute(args: { id: string; resource: string }) {
|
||||
const source = vscode.Uri.parse(args.resource);
|
||||
this._webviewManager.findPreview(source)?.copyImage(args.id);
|
||||
this.#webviewManager.findPreview(source)?.copyImage(args.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ import { MarkdownPreviewManager } from '../preview/previewManager';
|
||||
export class OpenImageCommand implements Command {
|
||||
public readonly id = '_markdown.openImage';
|
||||
|
||||
readonly #webviewManager: MarkdownPreviewManager;
|
||||
|
||||
public constructor(
|
||||
private readonly _webviewManager: MarkdownPreviewManager,
|
||||
) { }
|
||||
webviewManager: MarkdownPreviewManager,
|
||||
) {
|
||||
this.#webviewManager = webviewManager;
|
||||
}
|
||||
|
||||
public execute(args: { resource: string; imageSource: string }) {
|
||||
const source = vscode.Uri.parse(args.resource);
|
||||
this._webviewManager.openDocumentLink(args.imageSource, source);
|
||||
this.#webviewManager.openDocumentLink(args.imageSource, source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,19 @@ import { MarkdownPreviewManager } from '../preview/previewManager';
|
||||
export class RefreshPreviewCommand implements Command {
|
||||
public readonly id = 'markdown.preview.refresh';
|
||||
|
||||
readonly #webviewManager: MarkdownPreviewManager;
|
||||
readonly #engine: MarkdownItEngine;
|
||||
|
||||
public constructor(
|
||||
private readonly _webviewManager: MarkdownPreviewManager,
|
||||
private readonly _engine: MarkdownItEngine
|
||||
) { }
|
||||
webviewManager: MarkdownPreviewManager,
|
||||
engine: MarkdownItEngine
|
||||
) {
|
||||
this.#webviewManager = webviewManager;
|
||||
this.#engine = engine;
|
||||
}
|
||||
|
||||
public execute() {
|
||||
this._engine.cleanCache();
|
||||
this._webviewManager.refresh();
|
||||
this.#engine.cleanCache();
|
||||
this.#webviewManager.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,20 @@ import { MarkdownPreviewManager } from '../preview/previewManager';
|
||||
export class ReloadPlugins implements Command {
|
||||
public readonly id = 'markdown.api.reloadPlugins';
|
||||
|
||||
readonly #webviewManager: MarkdownPreviewManager;
|
||||
readonly #engine: MarkdownItEngine;
|
||||
|
||||
public constructor(
|
||||
private readonly _webviewManager: MarkdownPreviewManager,
|
||||
private readonly _engine: MarkdownItEngine,
|
||||
) { }
|
||||
webviewManager: MarkdownPreviewManager,
|
||||
engine: MarkdownItEngine,
|
||||
) {
|
||||
this.#webviewManager = webviewManager;
|
||||
this.#engine = engine;
|
||||
}
|
||||
|
||||
public execute(): void {
|
||||
this._engine.reloadPlugins();
|
||||
this._engine.cleanCache();
|
||||
this._webviewManager.refresh();
|
||||
this.#engine.reloadPlugins();
|
||||
this.#engine.cleanCache();
|
||||
this.#webviewManager.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@ import { ITextDocument } from '../types/textDocument';
|
||||
export class RenderDocument implements Command {
|
||||
public readonly id = 'markdown.api.render';
|
||||
|
||||
readonly #engine: MarkdownItEngine;
|
||||
|
||||
public constructor(
|
||||
private readonly _engine: MarkdownItEngine
|
||||
) { }
|
||||
engine: MarkdownItEngine
|
||||
) {
|
||||
this.#engine = engine;
|
||||
}
|
||||
|
||||
public async execute(document: ITextDocument | string): Promise<string> {
|
||||
return (await (this._engine.render(document))).html;
|
||||
return (await (this.#engine.render(document))).html;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,14 +53,20 @@ async function showPreview(
|
||||
export class ShowPreviewCommand implements Command {
|
||||
public readonly id = 'markdown.showPreview';
|
||||
|
||||
readonly #webviewManager: MarkdownPreviewManager;
|
||||
readonly #telemetryReporter: TelemetryReporter;
|
||||
|
||||
public constructor(
|
||||
private readonly _webviewManager: MarkdownPreviewManager,
|
||||
private readonly _telemetryReporter: TelemetryReporter
|
||||
) { }
|
||||
webviewManager: MarkdownPreviewManager,
|
||||
telemetryReporter: TelemetryReporter
|
||||
) {
|
||||
this.#webviewManager = webviewManager;
|
||||
this.#telemetryReporter = telemetryReporter;
|
||||
}
|
||||
|
||||
public execute(mainUri?: vscode.Uri, allUris?: vscode.Uri[], previewSettings?: DynamicPreviewSettings) {
|
||||
for (const uri of Array.isArray(allUris) ? allUris : [mainUri]) {
|
||||
showPreview(this._webviewManager, this._telemetryReporter, uri, {
|
||||
showPreview(this.#webviewManager, this.#telemetryReporter, uri, {
|
||||
sideBySide: false,
|
||||
locked: previewSettings?.locked
|
||||
});
|
||||
@@ -71,13 +77,19 @@ export class ShowPreviewCommand implements Command {
|
||||
export class ShowPreviewToSideCommand implements Command {
|
||||
public readonly id = 'markdown.showPreviewToSide';
|
||||
|
||||
readonly #webviewManager: MarkdownPreviewManager;
|
||||
readonly #telemetryReporter: TelemetryReporter;
|
||||
|
||||
public constructor(
|
||||
private readonly _webviewManager: MarkdownPreviewManager,
|
||||
private readonly _telemetryReporter: TelemetryReporter
|
||||
) { }
|
||||
webviewManager: MarkdownPreviewManager,
|
||||
telemetryReporter: TelemetryReporter
|
||||
) {
|
||||
this.#webviewManager = webviewManager;
|
||||
this.#telemetryReporter = telemetryReporter;
|
||||
}
|
||||
|
||||
public execute(uri?: vscode.Uri, previewSettings?: DynamicPreviewSettings) {
|
||||
showPreview(this._webviewManager, this._telemetryReporter, uri, {
|
||||
showPreview(this.#webviewManager, this.#telemetryReporter, uri, {
|
||||
sideBySide: true,
|
||||
locked: previewSettings?.locked
|
||||
});
|
||||
@@ -88,13 +100,19 @@ export class ShowPreviewToSideCommand implements Command {
|
||||
export class ShowLockedPreviewToSideCommand implements Command {
|
||||
public readonly id = 'markdown.showLockedPreviewToSide';
|
||||
|
||||
readonly #webviewManager: MarkdownPreviewManager;
|
||||
readonly #telemetryReporter: TelemetryReporter;
|
||||
|
||||
public constructor(
|
||||
private readonly _webviewManager: MarkdownPreviewManager,
|
||||
private readonly _telemetryReporter: TelemetryReporter
|
||||
) { }
|
||||
webviewManager: MarkdownPreviewManager,
|
||||
telemetryReporter: TelemetryReporter
|
||||
) {
|
||||
this.#webviewManager = webviewManager;
|
||||
this.#telemetryReporter = telemetryReporter;
|
||||
}
|
||||
|
||||
public execute(uri?: vscode.Uri) {
|
||||
showPreview(this._webviewManager, this._telemetryReporter, uri, {
|
||||
showPreview(this.#webviewManager, this.#telemetryReporter, uri, {
|
||||
sideBySide: true,
|
||||
locked: true
|
||||
});
|
||||
|
||||
@@ -12,19 +12,25 @@ import { isMarkdownFile } from '../util/file';
|
||||
export class ShowPreviewSecuritySelectorCommand implements Command {
|
||||
public readonly id = 'markdown.showPreviewSecuritySelector';
|
||||
|
||||
readonly #previewSecuritySelector: PreviewSecuritySelector;
|
||||
readonly #previewManager: MarkdownPreviewManager;
|
||||
|
||||
public constructor(
|
||||
private readonly _previewSecuritySelector: PreviewSecuritySelector,
|
||||
private readonly _previewManager: MarkdownPreviewManager
|
||||
) { }
|
||||
previewSecuritySelector: PreviewSecuritySelector,
|
||||
previewManager: MarkdownPreviewManager
|
||||
) {
|
||||
this.#previewSecuritySelector = previewSecuritySelector;
|
||||
this.#previewManager = previewManager;
|
||||
}
|
||||
|
||||
public execute(resource: string | undefined) {
|
||||
if (this._previewManager.activePreviewResource) {
|
||||
this._previewSecuritySelector.showSecuritySelectorForResource(this._previewManager.activePreviewResource);
|
||||
if (this.#previewManager.activePreviewResource) {
|
||||
this.#previewSecuritySelector.showSecuritySelectorForResource(this.#previewManager.activePreviewResource);
|
||||
} else if (resource) {
|
||||
const source = vscode.Uri.parse(resource);
|
||||
this._previewSecuritySelector.showSecuritySelectorForResource(source.query ? vscode.Uri.parse(source.query) : source);
|
||||
this.#previewSecuritySelector.showSecuritySelectorForResource(source.query ? vscode.Uri.parse(source.query) : source);
|
||||
} else if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) {
|
||||
this._previewSecuritySelector.showSecuritySelectorForResource(vscode.window.activeTextEditor.document.uri);
|
||||
this.#previewSecuritySelector.showSecuritySelectorForResource(vscode.window.activeTextEditor.document.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ import { MarkdownPreviewManager } from '../preview/previewManager';
|
||||
export class ShowSourceCommand implements Command {
|
||||
public readonly id = 'markdown.showSource';
|
||||
|
||||
readonly #previewManager: MarkdownPreviewManager;
|
||||
|
||||
public constructor(
|
||||
private readonly _previewManager: MarkdownPreviewManager
|
||||
) { }
|
||||
previewManager: MarkdownPreviewManager
|
||||
) {
|
||||
this.#previewManager = previewManager;
|
||||
}
|
||||
|
||||
public execute() {
|
||||
const { activePreviewResource, activePreviewResourceColumn } = this._previewManager;
|
||||
const { activePreviewResource, activePreviewResourceColumn } = this.#previewManager;
|
||||
if (activePreviewResource && activePreviewResourceColumn) {
|
||||
return vscode.workspace.openTextDocument(activePreviewResource).then(document => {
|
||||
return vscode.window.showTextDocument(document, activePreviewResourceColumn);
|
||||
|
||||
@@ -9,11 +9,15 @@ import { MarkdownPreviewManager } from '../preview/previewManager';
|
||||
export class ToggleLockCommand implements Command {
|
||||
public readonly id = 'markdown.preview.toggleLock';
|
||||
|
||||
readonly #previewManager: MarkdownPreviewManager;
|
||||
|
||||
public constructor(
|
||||
private readonly _previewManager: MarkdownPreviewManager
|
||||
) { }
|
||||
previewManager: MarkdownPreviewManager
|
||||
) {
|
||||
this.#previewManager = previewManager;
|
||||
}
|
||||
|
||||
public execute() {
|
||||
this._previewManager.toggleLock();
|
||||
this.#previewManager.toggleLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,18 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
||||
...Object.values(rootMediaMimesTypes).map(type => `${type}/*`),
|
||||
];
|
||||
|
||||
private readonly _yieldTo = [
|
||||
readonly #yieldTo = [
|
||||
vscode.DocumentDropOrPasteEditKind.Text,
|
||||
vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'link', 'image', 'attachment'), // Prefer notebook attachments
|
||||
];
|
||||
|
||||
readonly #parser: IMdParser;
|
||||
|
||||
constructor(
|
||||
private readonly _parser: IMdParser,
|
||||
) { }
|
||||
parser: IMdParser,
|
||||
) {
|
||||
this.#parser = parser;
|
||||
}
|
||||
|
||||
public async provideDocumentDropEdits(
|
||||
document: vscode.TextDocument,
|
||||
@@ -51,8 +55,8 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.DocumentDropEdit | undefined> {
|
||||
const edit = await this._createEdit(document, [new vscode.Range(position, position)], dataTransfer, {
|
||||
insert: this._getEnabled(document, 'editor.drop.enabled'),
|
||||
const edit = await this.#createEdit(document, [new vscode.Range(position, position)], dataTransfer, {
|
||||
insert: this.#getEnabled(document, 'editor.drop.enabled'),
|
||||
copyIntoWorkspace: vscode.workspace.getConfiguration('markdown', document).get<CopyFilesSettings>('editor.drop.copyIntoWorkspace', CopyFilesSettings.MediaFiles)
|
||||
}, undefined, token);
|
||||
|
||||
@@ -64,7 +68,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
||||
dropEdit.title = edit.label;
|
||||
dropEdit.kind = edit.kind;
|
||||
dropEdit.additionalEdit = edit.additionalEdits;
|
||||
dropEdit.yieldTo = [...this._yieldTo, ...edit.yieldTo];
|
||||
dropEdit.yieldTo = [...this.#yieldTo, ...edit.yieldTo];
|
||||
return dropEdit;
|
||||
}
|
||||
|
||||
@@ -75,8 +79,8 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
||||
context: vscode.DocumentPasteEditContext,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.DocumentPasteEdit[] | undefined> {
|
||||
const edit = await this._createEdit(document, ranges, dataTransfer, {
|
||||
insert: this._getEnabled(document, 'editor.paste.enabled'),
|
||||
const edit = await this.#createEdit(document, ranges, dataTransfer, {
|
||||
insert: this.#getEnabled(document, 'editor.paste.enabled'),
|
||||
copyIntoWorkspace: vscode.workspace.getConfiguration('markdown', document).get<CopyFilesSettings>('editor.paste.copyIntoWorkspace', CopyFilesSettings.MediaFiles)
|
||||
}, context, token);
|
||||
|
||||
@@ -86,11 +90,11 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
||||
|
||||
const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, edit.label, edit.kind);
|
||||
pasteEdit.additionalEdit = edit.additionalEdits;
|
||||
pasteEdit.yieldTo = [...this._yieldTo, ...edit.yieldTo];
|
||||
pasteEdit.yieldTo = [...this.#yieldTo, ...edit.yieldTo];
|
||||
return [pasteEdit];
|
||||
}
|
||||
|
||||
private _getEnabled(document: vscode.TextDocument, settingName: string): InsertMarkdownLink {
|
||||
#getEnabled(document: vscode.TextDocument, settingName: string): InsertMarkdownLink {
|
||||
const setting = vscode.workspace.getConfiguration('markdown', document).get<boolean | InsertMarkdownLink>(settingName, true);
|
||||
// Convert old boolean values to new enum setting
|
||||
if (setting === false) {
|
||||
@@ -102,7 +106,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
||||
}
|
||||
}
|
||||
|
||||
private async _createEdit(
|
||||
async #createEdit(
|
||||
document: vscode.TextDocument,
|
||||
ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
@@ -117,27 +121,27 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
||||
return;
|
||||
}
|
||||
|
||||
let edit = await this._createEditForMediaFiles(document, dataTransfer, settings.copyIntoWorkspace, token);
|
||||
let edit = await this.#createEditForMediaFiles(document, dataTransfer, settings.copyIntoWorkspace, token);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!edit) {
|
||||
edit = await this._createEditFromUriListData(document, ranges, dataTransfer, context, token);
|
||||
edit = await this.#createEditFromUriListData(document, ranges, dataTransfer, context, token);
|
||||
}
|
||||
|
||||
if (!edit || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await shouldInsertMarkdownLinkByDefault(this._parser, document, settings.insert, ranges, token))) {
|
||||
if (!(await shouldInsertMarkdownLinkByDefault(this.#parser, document, settings.insert, ranges, token))) {
|
||||
edit.yieldTo.push(vscode.DocumentDropOrPasteEditKind.Empty.append('uri'));
|
||||
}
|
||||
|
||||
return edit;
|
||||
}
|
||||
|
||||
private async _createEditFromUriListData(
|
||||
async #createEditFromUriListData(
|
||||
document: vscode.TextDocument,
|
||||
ranges: readonly vscode.Range[],
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
@@ -194,7 +198,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
|
||||
*
|
||||
* This tries copying files outside of the workspace into the workspace.
|
||||
*/
|
||||
private async _createEditForMediaFiles(
|
||||
async #createEditForMediaFiles(
|
||||
document: vscode.TextDocument,
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
copyIntoWorkspace: CopyFilesSettings,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { CopyFileConfiguration, getCopyFileConfiguration, parseGlob, resolveCopy
|
||||
|
||||
export class NewFilePathGenerator {
|
||||
|
||||
private readonly _usedPaths = new Set<string>();
|
||||
readonly #usedPaths = new Set<string>();
|
||||
|
||||
async getNewFilePath(
|
||||
document: vscode.TextDocument,
|
||||
@@ -33,13 +33,13 @@ export class NewFilePathGenerator {
|
||||
|
||||
const name = i === 0 ? baseName : `${baseName}-${i}`;
|
||||
const uri = vscode.Uri.joinPath(root, name + ext);
|
||||
if (this._wasPathAlreadyUsed(uri)) {
|
||||
if (this.#wasPathAlreadyUsed(uri)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try overwriting if it already exists
|
||||
if (config.overwriteBehavior === 'overwrite') {
|
||||
this._usedPaths.add(uri.toString());
|
||||
this.#usedPaths.add(uri.toString());
|
||||
return { uri, overwrite: true };
|
||||
}
|
||||
|
||||
@@ -47,17 +47,17 @@ export class NewFilePathGenerator {
|
||||
try {
|
||||
await vscode.workspace.fs.stat(uri);
|
||||
} catch {
|
||||
if (!this._wasPathAlreadyUsed(uri)) {
|
||||
if (!this.#wasPathAlreadyUsed(uri)) {
|
||||
// Does not exist
|
||||
this._usedPaths.add(uri.toString());
|
||||
this.#usedPaths.add(uri.toString());
|
||||
return { uri, overwrite: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _wasPathAlreadyUsed(uri: vscode.Uri) {
|
||||
return this._usedPaths.has(uri.toString());
|
||||
#wasPathAlreadyUsed(uri: vscode.Uri) {
|
||||
return this.#usedPaths.has(uri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,13 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
public static readonly pasteMimeTypes = [Mime.textPlain];
|
||||
|
||||
readonly #parser: IMdParser;
|
||||
|
||||
constructor(
|
||||
private readonly _parser: IMdParser,
|
||||
) { }
|
||||
parser: IMdParser,
|
||||
) {
|
||||
this.#parser = parser;
|
||||
}
|
||||
|
||||
async provideDocumentPasteEdits(
|
||||
document: vscode.TextDocument,
|
||||
@@ -64,7 +68,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
workspaceEdit.set(document.uri, edit.edits);
|
||||
pasteEdit.additionalEdit = workspaceEdit;
|
||||
|
||||
if (!(await shouldInsertMarkdownLinkByDefault(this._parser, document, pasteUrlSetting, ranges, token))) {
|
||||
if (!(await shouldInsertMarkdownLinkByDefault(this.#parser, document, pasteUrlSetting, ranges, token))) {
|
||||
pasteEdit.yieldTo = [
|
||||
vscode.DocumentDropOrPasteEditKind.Text,
|
||||
vscode.DocumentDropOrPasteEditKind.Empty.append('uri')
|
||||
|
||||
@@ -19,18 +19,18 @@ export enum DiagnosticCode {
|
||||
|
||||
class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider {
|
||||
|
||||
private static readonly _addToIgnoreLinksCommandId = '_markdown.addToIgnoreLinks';
|
||||
static readonly #addToIgnoreLinksCommandId = '_markdown.addToIgnoreLinks';
|
||||
|
||||
private static readonly _metadata: vscode.CodeActionProviderMetadata = {
|
||||
static readonly #metadata: vscode.CodeActionProviderMetadata = {
|
||||
providedCodeActionKinds: [
|
||||
vscode.CodeActionKind.QuickFix
|
||||
],
|
||||
};
|
||||
|
||||
public static register(selector: vscode.DocumentSelector, commandManager: CommandManager): vscode.Disposable {
|
||||
const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToIgnoreLinksQuickFixProvider(), AddToIgnoreLinksQuickFixProvider._metadata);
|
||||
const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToIgnoreLinksQuickFixProvider(), AddToIgnoreLinksQuickFixProvider.#metadata);
|
||||
const commandReg = commandManager.register({
|
||||
id: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId,
|
||||
id: AddToIgnoreLinksQuickFixProvider.#addToIgnoreLinksCommandId,
|
||||
execute(resource: vscode.Uri, path: string) {
|
||||
const settingId = 'validate.ignoredLinks';
|
||||
const config = vscode.workspace.getConfiguration('markdown', resource);
|
||||
@@ -58,7 +58,7 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider {
|
||||
vscode.CodeActionKind.QuickFix);
|
||||
|
||||
fix.command = {
|
||||
command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId,
|
||||
command: AddToIgnoreLinksQuickFixProvider.#addToIgnoreLinksCommandId,
|
||||
title: '',
|
||||
arguments: [document.uri, hrefText],
|
||||
};
|
||||
|
||||
@@ -13,9 +13,13 @@ export class FindFileReferencesCommand implements Command {
|
||||
|
||||
public readonly id = 'markdown.findAllFileReferences';
|
||||
|
||||
readonly #client: MdLanguageClient;
|
||||
|
||||
constructor(
|
||||
private readonly _client: MdLanguageClient,
|
||||
) { }
|
||||
client: MdLanguageClient,
|
||||
) {
|
||||
this.#client = client;
|
||||
}
|
||||
|
||||
public async execute(resource?: vscode.Uri) {
|
||||
resource ??= vscode.window.activeTextEditor?.document.uri;
|
||||
@@ -28,7 +32,7 @@ export class FindFileReferencesCommand implements Command {
|
||||
location: vscode.ProgressLocation.Window,
|
||||
title: vscode.l10n.t("Finding file references")
|
||||
}, async (_progress, token) => {
|
||||
const locations = (await this._client.getReferencesToFileInWorkspace(resource, token)).map(loc => {
|
||||
const locations = (await this.#client.getReferencesToFileInWorkspace(resource, token)).map(loc => {
|
||||
return new vscode.Location(vscode.Uri.parse(loc.uri), convertRange(loc.range));
|
||||
});
|
||||
|
||||
|
||||
@@ -33,46 +33,48 @@ interface RenameAction {
|
||||
|
||||
class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
|
||||
private readonly _delayer = new Delayer(50);
|
||||
private readonly _pendingRenames = new Set<RenameAction>();
|
||||
readonly #delayer = new Delayer(50);
|
||||
readonly #pendingRenames = new Set<RenameAction>();
|
||||
readonly #client: MdLanguageClient;
|
||||
|
||||
public constructor(
|
||||
private readonly _client: MdLanguageClient,
|
||||
client: MdLanguageClient,
|
||||
) {
|
||||
super();
|
||||
this.#client = client;
|
||||
|
||||
this._register(vscode.workspace.onDidRenameFiles(async (e) => {
|
||||
await Promise.all(e.files.map(async (rename) => {
|
||||
if (await this._shouldParticipateInLinkUpdate(rename.newUri)) {
|
||||
this._pendingRenames.add(rename);
|
||||
if (await this.#shouldParticipateInLinkUpdate(rename.newUri)) {
|
||||
this.#pendingRenames.add(rename);
|
||||
}
|
||||
}));
|
||||
|
||||
if (this._pendingRenames.size) {
|
||||
this._delayer.trigger(() => {
|
||||
if (this.#pendingRenames.size) {
|
||||
this.#delayer.trigger(() => {
|
||||
vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Window,
|
||||
title: vscode.l10n.t("Checking for Markdown links to update")
|
||||
}, () => this._flushRenames());
|
||||
}, () => this.#flushRenames());
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async _flushRenames(): Promise<void> {
|
||||
const renames = Array.from(this._pendingRenames);
|
||||
this._pendingRenames.clear();
|
||||
async #flushRenames(): Promise<void> {
|
||||
const renames = Array.from(this.#pendingRenames);
|
||||
this.#pendingRenames.clear();
|
||||
|
||||
const result = await this._getEditsForFileRename(renames, noopToken);
|
||||
const result = await this.#getEditsForFileRename(renames, noopToken);
|
||||
|
||||
if (result?.edit.size) {
|
||||
if (await this._confirmActionWithUser(result.resourcesBeingRenamed)) {
|
||||
if (await this.#confirmActionWithUser(result.resourcesBeingRenamed)) {
|
||||
await vscode.workspace.applyEdit(result.edit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _confirmActionWithUser(newResources: readonly vscode.Uri[]): Promise<boolean> {
|
||||
async #confirmActionWithUser(newResources: readonly vscode.Uri[]): Promise<boolean> {
|
||||
if (!newResources.length) {
|
||||
return false;
|
||||
}
|
||||
@@ -81,7 +83,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
const setting = config.get<UpdateLinksOnFileMoveSetting>(settingNames.enabled);
|
||||
switch (setting) {
|
||||
case UpdateLinksOnFileMoveSetting.Prompt:
|
||||
return this._promptUser(newResources);
|
||||
return this.#promptUser(newResources);
|
||||
case UpdateLinksOnFileMoveSetting.Always:
|
||||
return true;
|
||||
case UpdateLinksOnFileMoveSetting.Never:
|
||||
@@ -89,7 +91,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private async _shouldParticipateInLinkUpdate(newUri: vscode.Uri): Promise<boolean> {
|
||||
async #shouldParticipateInLinkUpdate(newUri: vscode.Uri): Promise<boolean> {
|
||||
const config = vscode.workspace.getConfiguration('markdown', newUri);
|
||||
const setting = config.get<UpdateLinksOnFileMoveSetting>(settingNames.enabled);
|
||||
if (setting === UpdateLinksOnFileMoveSetting.Never) {
|
||||
@@ -113,7 +115,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
private async _promptUser(newResources: readonly vscode.Uri[]): Promise<boolean> {
|
||||
async #promptUser(newResources: readonly vscode.Uri[]): Promise<boolean> {
|
||||
if (!newResources.length) {
|
||||
return false;
|
||||
}
|
||||
@@ -138,7 +140,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
const choice = await vscode.window.showInformationMessage(
|
||||
newResources.length === 1
|
||||
? vscode.l10n.t("Update Markdown links for '{0}'?", Utils.basename(newResources[0]))
|
||||
: this._getConfirmMessage(vscode.l10n.t("Update Markdown links for the following {0} files?", newResources.length), newResources), {
|
||||
: this.#getConfirmMessage(vscode.l10n.t("Update Markdown links for the following {0} files?", newResources.length), newResources), {
|
||||
modal: true,
|
||||
}, rejectItem, acceptItem, alwaysItem, neverItem);
|
||||
|
||||
@@ -154,7 +156,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
config.update(
|
||||
settingNames.enabled,
|
||||
UpdateLinksOnFileMoveSetting.Always,
|
||||
this._getConfigTargetScope(config, settingNames.enabled));
|
||||
this.#getConfigTargetScope(config, settingNames.enabled));
|
||||
return true;
|
||||
}
|
||||
case neverItem: {
|
||||
@@ -162,7 +164,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
config.update(
|
||||
settingNames.enabled,
|
||||
UpdateLinksOnFileMoveSetting.Never,
|
||||
this._getConfigTargetScope(config, settingNames.enabled));
|
||||
this.#getConfigTargetScope(config, settingNames.enabled));
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
@@ -171,8 +173,8 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private async _getEditsForFileRename(renames: readonly RenameAction[], token: vscode.CancellationToken): Promise<{ edit: vscode.WorkspaceEdit; resourcesBeingRenamed: vscode.Uri[] } | undefined> {
|
||||
const result = await this._client.getEditForFileRenames(renames.map(rename => ({ oldUri: rename.oldUri.toString(), newUri: rename.newUri.toString() })), token);
|
||||
async #getEditsForFileRename(renames: readonly RenameAction[], token: vscode.CancellationToken): Promise<{ edit: vscode.WorkspaceEdit; resourcesBeingRenamed: vscode.Uri[] } | undefined> {
|
||||
const result = await this.#client.getEditForFileRenames(renames.map(rename => ({ oldUri: rename.oldUri.toString(), newUri: rename.newUri.toString() })), token);
|
||||
if (!result?.edit.documentChanges?.length) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -192,7 +194,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
private _getConfirmMessage(start: string, resourcesToConfirm: readonly vscode.Uri[]): string {
|
||||
#getConfirmMessage(start: string, resourcesToConfirm: readonly vscode.Uri[]): string {
|
||||
const MAX_CONFIRM_FILES = 10;
|
||||
|
||||
const paths = [start];
|
||||
@@ -211,7 +213,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable {
|
||||
return paths.join('\n');
|
||||
}
|
||||
|
||||
private _getConfigTargetScope(config: vscode.WorkspaceConfiguration, settingsName: string): vscode.ConfigurationTarget {
|
||||
#getConfigTargetScope(config: vscode.WorkspaceConfiguration, settingsName: string): vscode.ConfigurationTarget {
|
||||
const inspected = config.inspect(settingsName);
|
||||
if (inspected?.workspaceFolderValue) {
|
||||
return vscode.ConfigurationTarget.WorkspaceFolder;
|
||||
|
||||
@@ -13,16 +13,20 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider
|
||||
|
||||
public static readonly metadataMime = 'application/vnd.vscode.markdown.updatelinks.metadata';
|
||||
|
||||
readonly #client: MdLanguageClient;
|
||||
|
||||
constructor(
|
||||
private readonly _client: MdLanguageClient,
|
||||
) { }
|
||||
client: MdLanguageClient,
|
||||
) {
|
||||
this.#client = client;
|
||||
}
|
||||
|
||||
async prepareDocumentPaste(document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<void> {
|
||||
if (!this._isEnabled(document)) {
|
||||
if (!this.#isEnabled(document)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = await this._client.prepareUpdatePastedLinks(document.uri, ranges, token);
|
||||
const metadata = await this.#client.prepareUpdatePastedLinks(document.uri, ranges, token);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
@@ -37,7 +41,7 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider
|
||||
context: vscode.DocumentPasteEditContext,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.DocumentPasteEdit[] | undefined> {
|
||||
if (!this._isEnabled(document)) {
|
||||
if (!this.#isEnabled(document)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,7 +60,7 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider
|
||||
// - copy empty line
|
||||
// - Copy with multiple cursors and paste into multiple locations
|
||||
// - ...
|
||||
const edits = await this._client.getUpdatePastedLinksEdit(document.uri, ranges.map(x => new vscode.TextEdit(x, text)), metadata, token);
|
||||
const edits = await this.#client.getUpdatePastedLinksEdit(document.uri, ranges.map(x => new vscode.TextEdit(x, text)), metadata, token);
|
||||
if (!edits?.length || token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
@@ -73,7 +77,7 @@ class UpdatePastedLinksEditProvider implements vscode.DocumentPasteEditProvider
|
||||
return [pasteEdit];
|
||||
}
|
||||
|
||||
private _isEnabled(document: vscode.TextDocument): boolean {
|
||||
#isEnabled(document: vscode.TextDocument): boolean {
|
||||
return vscode.workspace.getConfiguration('markdown', document.uri).get<boolean>('editor.updateLinksOnPaste.enabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ export interface ILogger {
|
||||
}
|
||||
|
||||
export class VsCodeOutputLogger extends Disposable implements ILogger {
|
||||
private _outputChannelValue?: vscode.LogOutputChannel;
|
||||
#outputChannelValue?: vscode.LogOutputChannel;
|
||||
|
||||
private get _outputChannel() {
|
||||
this._outputChannelValue ??= this._register(vscode.window.createOutputChannel('Markdown', { log: true }));
|
||||
return this._outputChannelValue;
|
||||
get #outputChannel() {
|
||||
this.#outputChannelValue ??= this._register(vscode.window.createOutputChannel('Markdown', { log: true }));
|
||||
return this.#outputChannelValue;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@@ -24,6 +24,6 @@ export class VsCodeOutputLogger extends Disposable implements ILogger {
|
||||
}
|
||||
|
||||
public trace(title: string, message: string, data?: any): void {
|
||||
this._outputChannel.trace(`${title}: ${message}`, ...(data ? [JSON.stringify(data, null, 4)] : []));
|
||||
this.#outputChannel.trace(`${title}: ${message}`, ...(data ? [JSON.stringify(data, null, 4)] : []));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,37 +44,37 @@ const pluginSourceMap: MarkdownIt.PluginSimple = (md): void => {
|
||||
type MarkdownItConfig = Readonly<Required<Pick<MarkdownIt.Options, 'breaks' | 'linkify' | 'typographer'>>>;
|
||||
|
||||
class TokenCache {
|
||||
private _cachedDocument?: {
|
||||
#cachedDocument?: {
|
||||
readonly uri: vscode.Uri;
|
||||
readonly version: number;
|
||||
readonly config: MarkdownItConfig;
|
||||
};
|
||||
private _tokens?: MarkdownIt.Token[];
|
||||
#tokens?: MarkdownIt.Token[];
|
||||
|
||||
public tryGetCached(document: ITextDocument, config: MarkdownItConfig): MarkdownIt.Token[] | undefined {
|
||||
if (this._cachedDocument
|
||||
&& this._cachedDocument.uri.toString() === document.uri.toString()
|
||||
&& document.version >= 0 && this._cachedDocument.version === document.version
|
||||
&& this._cachedDocument.config.breaks === config.breaks
|
||||
&& this._cachedDocument.config.linkify === config.linkify
|
||||
if (this.#cachedDocument
|
||||
&& this.#cachedDocument.uri.toString() === document.uri.toString()
|
||||
&& document.version >= 0 && this.#cachedDocument.version === document.version
|
||||
&& this.#cachedDocument.config.breaks === config.breaks
|
||||
&& this.#cachedDocument.config.linkify === config.linkify
|
||||
) {
|
||||
return this._tokens;
|
||||
return this.#tokens;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public update(document: ITextDocument, config: MarkdownItConfig, tokens: MarkdownIt.Token[]) {
|
||||
this._cachedDocument = {
|
||||
this.#cachedDocument = {
|
||||
uri: document.uri,
|
||||
version: document.version,
|
||||
config,
|
||||
};
|
||||
this._tokens = tokens;
|
||||
this.#tokens = tokens;
|
||||
}
|
||||
|
||||
public clean(): void {
|
||||
this._cachedDocument = undefined;
|
||||
this._tokens = undefined;
|
||||
this.#cachedDocument = undefined;
|
||||
this.#tokens = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,40 +98,45 @@ export interface IMdParser {
|
||||
|
||||
export class MarkdownItEngine implements IMdParser {
|
||||
|
||||
private _md?: Promise<MarkdownIt>;
|
||||
#md?: Promise<MarkdownIt>;
|
||||
|
||||
private readonly _tokenCache = new TokenCache();
|
||||
readonly #tokenCache = new TokenCache();
|
||||
|
||||
public readonly slugifier: ISlugifier;
|
||||
|
||||
public constructor(
|
||||
private readonly _contributionProvider: MarkdownContributionProvider,
|
||||
slugifier: ISlugifier,
|
||||
private readonly _logger: ILogger,
|
||||
) {
|
||||
this.slugifier = slugifier;
|
||||
readonly #contributionProvider: MarkdownContributionProvider;
|
||||
readonly #logger: ILogger;
|
||||
|
||||
_contributionProvider.onContributionsChanged(() => {
|
||||
public constructor(
|
||||
contributionProvider: MarkdownContributionProvider,
|
||||
slugifier: ISlugifier,
|
||||
logger: ILogger,
|
||||
) {
|
||||
this.#contributionProvider = contributionProvider;
|
||||
this.slugifier = slugifier;
|
||||
this.#logger = logger;
|
||||
|
||||
contributionProvider.onContributionsChanged(() => {
|
||||
// Markdown plugin contributions may have changed
|
||||
this._md = undefined;
|
||||
this._tokenCache.clean();
|
||||
this.#md = undefined;
|
||||
this.#tokenCache.clean();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public async getEngine(resource: vscode.Uri | undefined): Promise<MarkdownIt> {
|
||||
const config = this._getConfig(resource);
|
||||
return this._getEngine(config);
|
||||
const config = this.#getConfig(resource);
|
||||
return this.#getEngine(config);
|
||||
}
|
||||
|
||||
private async _getEngine(config: MarkdownItConfig): Promise<MarkdownIt> {
|
||||
if (!this._md) {
|
||||
this._md = (async () => {
|
||||
async #getEngine(config: MarkdownItConfig): Promise<MarkdownIt> {
|
||||
if (!this.#md) {
|
||||
this.#md = (async () => {
|
||||
const markdownIt = await import('markdown-it');
|
||||
let md: MarkdownIt = markdownIt.default(await getMarkdownOptions(() => md));
|
||||
md.linkify.set({ fuzzyLink: false });
|
||||
|
||||
for (const plugin of this._contributionProvider.contributions.markdownItPlugins.values()) {
|
||||
for (const plugin of this.#contributionProvider.contributions.markdownItPlugins.values()) {
|
||||
try {
|
||||
md = (await plugin)(md);
|
||||
} catch (e) {
|
||||
@@ -154,43 +159,43 @@ export class MarkdownItEngine implements IMdParser {
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
||||
});
|
||||
|
||||
this._addImageRenderer(md);
|
||||
this._addFencedRenderer(md);
|
||||
this._addLinkNormalizer(md);
|
||||
this._addLinkValidator(md);
|
||||
this._addNamedHeaders(md);
|
||||
this._addLinkRenderer(md);
|
||||
this.#addImageRenderer(md);
|
||||
this.#addFencedRenderer(md);
|
||||
this.#addLinkNormalizer(md);
|
||||
this.#addLinkValidator(md);
|
||||
this.#addNamedHeaders(md);
|
||||
this.#addLinkRenderer(md);
|
||||
md.use(pluginSourceMap);
|
||||
return md;
|
||||
})();
|
||||
}
|
||||
|
||||
const md = await this._md!;
|
||||
const md = await this.#md!;
|
||||
md.set(config);
|
||||
return md;
|
||||
}
|
||||
|
||||
public reloadPlugins() {
|
||||
this._md = undefined;
|
||||
this.#md = undefined;
|
||||
}
|
||||
|
||||
private _tokenizeDocument(
|
||||
#tokenizeDocument(
|
||||
document: ITextDocument,
|
||||
config: MarkdownItConfig,
|
||||
engine: MarkdownIt
|
||||
): MarkdownIt.Token[] {
|
||||
const cached = this._tokenCache.tryGetCached(document, config);
|
||||
const cached = this.#tokenCache.tryGetCached(document, config);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
this._logger.trace('MarkdownItEngine', `tokenizeDocument - ${document.uri}`);
|
||||
const tokens = this._tokenizeString(document.getText(), engine);
|
||||
this._tokenCache.update(document, config, tokens);
|
||||
this.#logger.trace('MarkdownItEngine', `tokenizeDocument - ${document.uri}`);
|
||||
const tokens = this.#tokenizeString(document.getText(), engine);
|
||||
this.#tokenCache.update(document, config, tokens);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private _tokenizeString(text: string, engine: MarkdownIt) {
|
||||
#tokenizeString(text: string, engine: MarkdownIt) {
|
||||
const env: RenderEnv = {
|
||||
currentDocument: undefined,
|
||||
containingImages: new Set<string>(),
|
||||
@@ -201,12 +206,12 @@ export class MarkdownItEngine implements IMdParser {
|
||||
}
|
||||
|
||||
public async render(input: ITextDocument | string, resourceProvider?: WebviewResourceProvider): Promise<RenderOutput> {
|
||||
const config = this._getConfig(typeof input === 'string' ? undefined : input.uri);
|
||||
const engine = await this._getEngine(config);
|
||||
const config = this.#getConfig(typeof input === 'string' ? undefined : input.uri);
|
||||
const engine = await this.#getEngine(config);
|
||||
|
||||
const tokens = typeof input === 'string'
|
||||
? this._tokenizeString(input, engine)
|
||||
: this._tokenizeDocument(input, config, engine);
|
||||
? this.#tokenizeString(input, engine)
|
||||
: this.#tokenizeDocument(input, config, engine);
|
||||
|
||||
const env: RenderEnv = {
|
||||
containingImages: new Set<string>(),
|
||||
@@ -227,16 +232,16 @@ export class MarkdownItEngine implements IMdParser {
|
||||
}
|
||||
|
||||
public async tokenize(document: ITextDocument): Promise<MarkdownIt.Token[]> {
|
||||
const config = this._getConfig(document.uri);
|
||||
const engine = await this._getEngine(config);
|
||||
return this._tokenizeDocument(document, config, engine);
|
||||
const config = this.#getConfig(document.uri);
|
||||
const engine = await this.#getEngine(config);
|
||||
return this.#tokenizeDocument(document, config, engine);
|
||||
}
|
||||
|
||||
public cleanCache(): void {
|
||||
this._tokenCache.clean();
|
||||
this.#tokenCache.clean();
|
||||
}
|
||||
|
||||
private _getConfig(resource?: vscode.Uri): MarkdownItConfig {
|
||||
#getConfig(resource?: vscode.Uri): MarkdownItConfig {
|
||||
const config = MarkdownPreviewConfiguration.getForResource(resource ?? null);
|
||||
return {
|
||||
breaks: config.previewLineBreaks,
|
||||
@@ -245,7 +250,7 @@ export class MarkdownItEngine implements IMdParser {
|
||||
};
|
||||
}
|
||||
|
||||
private _addImageRenderer(md: MarkdownIt): void {
|
||||
#addImageRenderer(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules.image;
|
||||
md.renderer.rules.image = (tokens: MarkdownIt.Token[], idx: number, options, env: RenderEnv, self) => {
|
||||
const token = tokens[idx];
|
||||
@@ -254,7 +259,7 @@ export class MarkdownItEngine implements IMdParser {
|
||||
env.containingImages?.add(src);
|
||||
|
||||
if (!token.attrGet('data-src')) {
|
||||
token.attrSet('src', this._toResourceUri(src, env.currentDocument, env.resourceProvider));
|
||||
token.attrSet('src', this.#toResourceUri(src, env.currentDocument, env.resourceProvider));
|
||||
token.attrSet('data-src', src);
|
||||
}
|
||||
}
|
||||
@@ -267,7 +272,7 @@ export class MarkdownItEngine implements IMdParser {
|
||||
};
|
||||
}
|
||||
|
||||
private _addFencedRenderer(md: MarkdownIt): void {
|
||||
#addFencedRenderer(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules['fenced'];
|
||||
md.renderer.rules['fenced'] = (tokens: MarkdownIt.Token[], idx: number, options, env, self) => {
|
||||
const token = tokens[idx];
|
||||
@@ -283,7 +288,7 @@ export class MarkdownItEngine implements IMdParser {
|
||||
};
|
||||
}
|
||||
|
||||
private _addLinkNormalizer(md: MarkdownIt): void {
|
||||
#addLinkNormalizer(md: MarkdownIt): void {
|
||||
const normalizeLink = md.normalizeLink;
|
||||
md.normalizeLink = (link: string) => {
|
||||
try {
|
||||
@@ -299,7 +304,7 @@ export class MarkdownItEngine implements IMdParser {
|
||||
};
|
||||
}
|
||||
|
||||
private _addLinkValidator(md: MarkdownIt): void {
|
||||
#addLinkValidator(md: MarkdownIt): void {
|
||||
const validateLink = md.validateLink;
|
||||
md.validateLink = (link: string) => {
|
||||
return validateLink(link)
|
||||
@@ -309,10 +314,10 @@ export class MarkdownItEngine implements IMdParser {
|
||||
};
|
||||
}
|
||||
|
||||
private _addNamedHeaders(md: MarkdownIt): void {
|
||||
#addNamedHeaders(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules.heading_open;
|
||||
md.renderer.rules.heading_open = (tokens: MarkdownIt.Token[], idx: number, options, env: unknown, self) => {
|
||||
const title = this._tokenToPlainText(tokens[idx + 1]);
|
||||
const title = this.#tokenToPlainText(tokens[idx + 1]);
|
||||
const slug = (env as RenderEnv).slugifier ? (env as RenderEnv).slugifier.add(title) : this.slugifier.fromHeading(title);
|
||||
tokens[idx].attrSet('id', slug.value);
|
||||
|
||||
@@ -324,9 +329,9 @@ export class MarkdownItEngine implements IMdParser {
|
||||
};
|
||||
}
|
||||
|
||||
private _tokenToPlainText(token: MarkdownIt.Token): string {
|
||||
#tokenToPlainText(token: MarkdownIt.Token): string {
|
||||
if (token.children) {
|
||||
return token.children.map(x => this._tokenToPlainText(x)).join('');
|
||||
return token.children.map(x => this.#tokenToPlainText(x)).join('');
|
||||
}
|
||||
|
||||
switch (token.type) {
|
||||
@@ -339,7 +344,7 @@ export class MarkdownItEngine implements IMdParser {
|
||||
}
|
||||
}
|
||||
|
||||
private _addLinkRenderer(md: MarkdownIt): void {
|
||||
#addLinkRenderer(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules.link_open;
|
||||
|
||||
md.renderer.rules.link_open = (tokens: MarkdownIt.Token[], idx: number, options, env, self) => {
|
||||
@@ -357,7 +362,7 @@ export class MarkdownItEngine implements IMdParser {
|
||||
};
|
||||
}
|
||||
|
||||
private _toResourceUri(href: string, currentDocument: vscode.Uri | undefined, resourceProvider: WebviewResourceProvider | undefined): string {
|
||||
#toResourceUri(href: string, currentDocument: vscode.Uri | undefined, resourceProvider: WebviewResourceProvider | undefined): string {
|
||||
try {
|
||||
// Support file:// links
|
||||
if (isOfScheme(Schemes.file, href)) {
|
||||
|
||||
@@ -119,36 +119,38 @@ export interface MarkdownContributionProvider {
|
||||
|
||||
class VSCodeExtensionMarkdownContributionProvider extends Disposable implements MarkdownContributionProvider {
|
||||
|
||||
private _contributions?: MarkdownContributions;
|
||||
#contributions?: MarkdownContributions;
|
||||
readonly #extensionContext: vscode.ExtensionContext;
|
||||
|
||||
public constructor(
|
||||
private readonly _extensionContext: vscode.ExtensionContext,
|
||||
extensionContext: vscode.ExtensionContext,
|
||||
) {
|
||||
super();
|
||||
this.#extensionContext = extensionContext;
|
||||
|
||||
this._register(vscode.extensions.onDidChange(() => {
|
||||
const currentContributions = this._getCurrentContributions();
|
||||
const existingContributions = this._contributions || MarkdownContributions.Empty;
|
||||
const currentContributions = this.#getCurrentContributions();
|
||||
const existingContributions = this.#contributions || MarkdownContributions.Empty;
|
||||
if (!MarkdownContributions.equal(existingContributions, currentContributions)) {
|
||||
this._contributions = currentContributions;
|
||||
this._onContributionsChanged.fire(this);
|
||||
this.#contributions = currentContributions;
|
||||
this.#onContributionsChanged.fire(this);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public get extensionUri() {
|
||||
return this._extensionContext.extensionUri;
|
||||
return this.#extensionContext.extensionUri;
|
||||
}
|
||||
|
||||
private readonly _onContributionsChanged = this._register(new vscode.EventEmitter<this>());
|
||||
public readonly onContributionsChanged = this._onContributionsChanged.event;
|
||||
readonly #onContributionsChanged = this._register(new vscode.EventEmitter<this>());
|
||||
public readonly onContributionsChanged = this.#onContributionsChanged.event;
|
||||
|
||||
public get contributions(): MarkdownContributions {
|
||||
this._contributions ??= this._getCurrentContributions();
|
||||
return this._contributions;
|
||||
this.#contributions ??= this.#getCurrentContributions();
|
||||
return this.#contributions;
|
||||
}
|
||||
|
||||
private _getCurrentContributions(): MarkdownContributions {
|
||||
#getCurrentContributions(): MarkdownContributions {
|
||||
return vscode.extensions.all
|
||||
.map(MarkdownContributions.fromExtension)
|
||||
.reduce(MarkdownContributions.merge, MarkdownContributions.Empty);
|
||||
|
||||
@@ -41,16 +41,28 @@ export interface ImageInfo {
|
||||
}
|
||||
|
||||
export class MdDocumentRenderer {
|
||||
|
||||
readonly #engine: MarkdownItEngine;
|
||||
readonly #context: vscode.ExtensionContext;
|
||||
readonly #cspArbiter: ContentSecurityPolicyArbiter;
|
||||
readonly #contributionProvider: MarkdownContributionProvider;
|
||||
readonly #logger: ILogger;
|
||||
|
||||
constructor(
|
||||
private readonly _engine: MarkdownItEngine,
|
||||
private readonly _context: vscode.ExtensionContext,
|
||||
private readonly _cspArbiter: ContentSecurityPolicyArbiter,
|
||||
private readonly _contributionProvider: MarkdownContributionProvider,
|
||||
private readonly _logger: ILogger
|
||||
engine: MarkdownItEngine,
|
||||
context: vscode.ExtensionContext,
|
||||
cspArbiter: ContentSecurityPolicyArbiter,
|
||||
contributionProvider: MarkdownContributionProvider,
|
||||
logger: ILogger
|
||||
) {
|
||||
this.#engine = engine;
|
||||
this.#context = context;
|
||||
this.#cspArbiter = cspArbiter;
|
||||
this.#contributionProvider = contributionProvider;
|
||||
this.#logger = logger;
|
||||
this.iconPath = {
|
||||
dark: vscode.Uri.joinPath(this._context.extensionUri, 'media', 'preview-dark.svg'),
|
||||
light: vscode.Uri.joinPath(this._context.extensionUri, 'media', 'preview-light.svg'),
|
||||
dark: vscode.Uri.joinPath(this.#context.extensionUri, 'media', 'preview-dark.svg'),
|
||||
light: vscode.Uri.joinPath(this.#context.extensionUri, 'media', 'preview-light.svg'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,15 +88,15 @@ export class MdDocumentRenderer {
|
||||
scrollPreviewWithEditor: config.scrollPreviewWithEditor,
|
||||
scrollEditorWithPreview: config.scrollEditorWithPreview,
|
||||
doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor,
|
||||
disableSecurityWarnings: this._cspArbiter.shouldDisableSecurityWarnings(),
|
||||
disableSecurityWarnings: this.#cspArbiter.shouldDisableSecurityWarnings(),
|
||||
webviewResourceRoot: resourceProvider.asWebviewUri(markdownDocument.uri).toString(),
|
||||
};
|
||||
|
||||
this._logger.trace('DocumentRenderer', `provideTextDocumentContent - ${markdownDocument.uri}`, initialData);
|
||||
this.#logger.trace('DocumentRenderer', `provideTextDocumentContent - ${markdownDocument.uri}`, initialData);
|
||||
|
||||
// Content Security Policy
|
||||
const nonce = generateUuid();
|
||||
const csp = this._getCsp(resourceProvider, sourceUri, nonce);
|
||||
const csp = this.#getCsp(resourceProvider, sourceUri, nonce);
|
||||
|
||||
const body = await this.renderBody(markdownDocument, resourceProvider);
|
||||
if (token.isCancellationRequested) {
|
||||
@@ -92,7 +104,7 @@ export class MdDocumentRenderer {
|
||||
}
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html style="${escapeAttribute(this._getSettingsOverrideStyles(config))}">
|
||||
<html style="${escapeAttribute(this.#getSettingsOverrideStyles(config))}">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(csp)}">
|
||||
@@ -101,12 +113,12 @@ export class MdDocumentRenderer {
|
||||
data-strings="${escapeAttribute(JSON.stringify(previewStrings))}"
|
||||
data-state="${escapeAttribute(JSON.stringify(state || {}))}"
|
||||
data-initial-md-content="${escapeAttribute(body.html)}">
|
||||
<script src="${this._extensionResourcePath(resourceProvider, 'pre.js')}" nonce="${nonce}"></script>
|
||||
${this._getStyles(resourceProvider, sourceUri, config, imageInfo)}
|
||||
<script src="${this.#extensionResourcePath(resourceProvider, 'pre.js')}" nonce="${nonce}"></script>
|
||||
${this.#getStyles(resourceProvider, sourceUri, config, imageInfo)}
|
||||
<base href="${resourceProvider.asWebviewUri(markdownDocument.uri)}">
|
||||
</head>
|
||||
<body class="vscode-body ${config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${config.wordWrap ? 'wordWrap' : ''} ${config.markEditorSelection ? 'showEditorSelection' : ''}">
|
||||
${this._getScripts(resourceProvider, nonce)}
|
||||
${this.#getScripts(resourceProvider, nonce)}
|
||||
</body>
|
||||
</html>`;
|
||||
return {
|
||||
@@ -119,7 +131,7 @@ export class MdDocumentRenderer {
|
||||
markdownDocument: vscode.TextDocument,
|
||||
resourceProvider: WebviewResourceProvider,
|
||||
): Promise<MarkdownContentProviderOutput> {
|
||||
const rendered = await this._engine.render(markdownDocument, resourceProvider);
|
||||
const rendered = await this.#engine.render(markdownDocument, resourceProvider);
|
||||
const html = `<div class="markdown-body" dir="auto">${rendered.html}<div class="code-line" data-line="${markdownDocument.lineCount}"></div></div>`;
|
||||
return {
|
||||
html,
|
||||
@@ -138,13 +150,13 @@ export class MdDocumentRenderer {
|
||||
</html>`;
|
||||
}
|
||||
|
||||
private _extensionResourcePath(resourceProvider: WebviewResourceProvider, mediaFile: string): string {
|
||||
#extensionResourcePath(resourceProvider: WebviewResourceProvider, mediaFile: string): string {
|
||||
const webviewResource = resourceProvider.asWebviewUri(
|
||||
vscode.Uri.joinPath(this._context.extensionUri, 'media', mediaFile));
|
||||
vscode.Uri.joinPath(this.#context.extensionUri, 'media', mediaFile));
|
||||
return webviewResource.toString();
|
||||
}
|
||||
|
||||
private _fixHref(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, href: string): string {
|
||||
#fixHref(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, href: string): string {
|
||||
if (!href) {
|
||||
return href;
|
||||
}
|
||||
@@ -168,18 +180,18 @@ export class MdDocumentRenderer {
|
||||
return resourceProvider.asWebviewUri(vscode.Uri.joinPath(uri.Utils.dirname(resource), href)).toString();
|
||||
}
|
||||
|
||||
private _computeCustomStyleSheetIncludes(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string {
|
||||
#computeCustomStyleSheetIncludes(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string {
|
||||
if (!Array.isArray(config.styles)) {
|
||||
return '';
|
||||
}
|
||||
const out: string[] = [];
|
||||
for (const style of config.styles) {
|
||||
out.push(`<link rel="stylesheet" class="code-user-style" data-source="${escapeAttribute(style)}" href="${escapeAttribute(this._fixHref(resourceProvider, resource, style))}" type="text/css" media="screen">`);
|
||||
out.push(`<link rel="stylesheet" class="code-user-style" data-source="${escapeAttribute(style)}" href="${escapeAttribute(this.#fixHref(resourceProvider, resource, style))}" type="text/css" media="screen">`);
|
||||
}
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
private _getSettingsOverrideStyles(config: MarkdownPreviewConfiguration): string {
|
||||
#getSettingsOverrideStyles(config: MarkdownPreviewConfiguration): string {
|
||||
return [
|
||||
config.fontFamily ? `--markdown-font-family: ${config.fontFamily};` : '',
|
||||
isNaN(config.fontSize) ? '' : `--markdown-font-size: ${config.fontSize}px;`,
|
||||
@@ -187,7 +199,7 @@ export class MdDocumentRenderer {
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
private _getImageStabilizerStyles(imageInfo: readonly ImageInfo[]): string {
|
||||
#getImageStabilizerStyles(imageInfo: readonly ImageInfo[]): string {
|
||||
if (!imageInfo.length) {
|
||||
return '';
|
||||
}
|
||||
@@ -204,20 +216,20 @@ export class MdDocumentRenderer {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private _getStyles(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration, imageInfo: readonly ImageInfo[]): string {
|
||||
#getStyles(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration, imageInfo: readonly ImageInfo[]): string {
|
||||
const baseStyles: string[] = [];
|
||||
for (const resource of this._contributionProvider.contributions.previewStyles) {
|
||||
for (const resource of this.#contributionProvider.contributions.previewStyles) {
|
||||
baseStyles.push(`<link rel="stylesheet" type="text/css" href="${escapeAttribute(resourceProvider.asWebviewUri(resource))}">`);
|
||||
}
|
||||
|
||||
return `${baseStyles.join('\n')}
|
||||
${this._computeCustomStyleSheetIncludes(resourceProvider, resource, config)}
|
||||
${this._getImageStabilizerStyles(imageInfo)}`;
|
||||
${this.#computeCustomStyleSheetIncludes(resourceProvider, resource, config)}
|
||||
${this.#getImageStabilizerStyles(imageInfo)}`;
|
||||
}
|
||||
|
||||
private _getScripts(resourceProvider: WebviewResourceProvider, nonce: string): string {
|
||||
#getScripts(resourceProvider: WebviewResourceProvider, nonce: string): string {
|
||||
const out: string[] = [];
|
||||
for (const resource of this._contributionProvider.contributions.previewScripts) {
|
||||
for (const resource of this.#contributionProvider.contributions.previewScripts) {
|
||||
out.push(`<script async
|
||||
src="${escapeAttribute(resourceProvider.asWebviewUri(resource))}"
|
||||
nonce="${nonce}"
|
||||
@@ -226,13 +238,13 @@ export class MdDocumentRenderer {
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
private _getCsp(
|
||||
#getCsp(
|
||||
provider: WebviewResourceProvider,
|
||||
resource: vscode.Uri,
|
||||
nonce: string
|
||||
): string {
|
||||
const rule = provider.cspSource.split(';')[0];
|
||||
switch (this._cspArbiter.getSecurityLevelForResource(resource)) {
|
||||
switch (this.#cspArbiter.getSecurityLevelForResource(resource)) {
|
||||
case MarkdownPreviewSecurityLevel.AllowInsecureContent:
|
||||
return `default-src 'none'; img-src 'self' ${rule} http: https: data:; media-src 'self' ${rule} http: https: data:; script-src 'nonce-${nonce}'; style-src 'self' ${rule} 'unsafe-inline' http: https: data:; font-src 'self' ${rule} http: https: data:;`;
|
||||
|
||||
|
||||
@@ -21,16 +21,16 @@ import type { FromWebviewMessage, ToWebviewMessage } from '../../types/previewMe
|
||||
export class PreviewDocumentVersion {
|
||||
|
||||
public readonly resource: vscode.Uri;
|
||||
private readonly _version: number;
|
||||
readonly #version: number;
|
||||
|
||||
public constructor(document: vscode.TextDocument) {
|
||||
this.resource = document.uri;
|
||||
this._version = document.version;
|
||||
this.#version = document.version;
|
||||
}
|
||||
|
||||
public equals(other: PreviewDocumentVersion): boolean {
|
||||
return areUrisEqual(this.resource, other.resource)
|
||||
&& this._version === other._version;
|
||||
&& this.#version === other.#version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,59 +42,73 @@ interface MarkdownPreviewDelegate {
|
||||
|
||||
class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
|
||||
private static readonly _unwatchedImageSchemes = new Set(['https', 'http', 'data']);
|
||||
static readonly #unwatchedImageSchemes = new Set(['https', 'http', 'data']);
|
||||
|
||||
private _disposed: boolean = false;
|
||||
#disposed: boolean = false;
|
||||
|
||||
private readonly _delay = 300;
|
||||
private _throttleTimer: any;
|
||||
readonly #delay = 300;
|
||||
#throttleTimer: any;
|
||||
|
||||
private readonly _resource: vscode.Uri;
|
||||
private readonly _webviewPanel: vscode.WebviewPanel;
|
||||
readonly #resource: vscode.Uri;
|
||||
readonly #webviewPanel: vscode.WebviewPanel;
|
||||
|
||||
private _line: number | undefined;
|
||||
private readonly _scrollToFragment: string | undefined;
|
||||
private _firstUpdate = true;
|
||||
private _currentVersion?: PreviewDocumentVersion;
|
||||
private _isScrolling = false;
|
||||
#line: number | undefined;
|
||||
readonly #scrollToFragment: string | undefined;
|
||||
#firstUpdate = true;
|
||||
#currentVersion?: PreviewDocumentVersion;
|
||||
#isScrolling = false;
|
||||
|
||||
private _imageInfo: readonly ImageInfo[] = [];
|
||||
private readonly _fileWatchersBySrc = new Map</* src: */ string, vscode.FileSystemWatcher>();
|
||||
#imageInfo: readonly ImageInfo[] = [];
|
||||
readonly #fileWatchersBySrc = new Map</* src: */ string, vscode.FileSystemWatcher>();
|
||||
|
||||
private readonly _onScrollEmitter = this._register(new vscode.EventEmitter<LastScrollLocation>());
|
||||
public readonly onScroll = this._onScrollEmitter.event;
|
||||
readonly #onScrollEmitter = this._register(new vscode.EventEmitter<LastScrollLocation>());
|
||||
public readonly onScroll = this.#onScrollEmitter.event;
|
||||
|
||||
private readonly _disposeCts = this._register(new vscode.CancellationTokenSource());
|
||||
readonly #disposeCts = this._register(new vscode.CancellationTokenSource());
|
||||
|
||||
readonly #delegate: MarkdownPreviewDelegate;
|
||||
readonly #contentProvider: MdDocumentRenderer;
|
||||
readonly #previewConfigurations: MarkdownPreviewConfigurationManager;
|
||||
readonly #logger: ILogger;
|
||||
readonly #contributionProvider: MarkdownContributionProvider;
|
||||
readonly #opener: MdLinkOpener;
|
||||
|
||||
constructor(
|
||||
webview: vscode.WebviewPanel,
|
||||
resource: vscode.Uri,
|
||||
startingScroll: StartingScrollLocation | undefined,
|
||||
private readonly _delegate: MarkdownPreviewDelegate,
|
||||
private readonly _contentProvider: MdDocumentRenderer,
|
||||
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
private readonly _logger: ILogger,
|
||||
private readonly _contributionProvider: MarkdownContributionProvider,
|
||||
private readonly _opener: MdLinkOpener,
|
||||
delegate: MarkdownPreviewDelegate,
|
||||
contentProvider: MdDocumentRenderer,
|
||||
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
logger: ILogger,
|
||||
contributionProvider: MarkdownContributionProvider,
|
||||
opener: MdLinkOpener,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._webviewPanel = webview;
|
||||
this._resource = resource;
|
||||
this.#delegate = delegate;
|
||||
this.#contentProvider = contentProvider;
|
||||
this.#previewConfigurations = previewConfigurations;
|
||||
this.#logger = logger;
|
||||
this.#contributionProvider = contributionProvider;
|
||||
this.#opener = opener;
|
||||
|
||||
this.#webviewPanel = webview;
|
||||
this.#resource = resource;
|
||||
|
||||
switch (startingScroll?.type) {
|
||||
case 'line':
|
||||
if (!isNaN(startingScroll.line!)) {
|
||||
this._line = startingScroll.line;
|
||||
this.#line = startingScroll.line;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'fragment':
|
||||
this._scrollToFragment = startingScroll.fragment;
|
||||
this.#scrollToFragment = startingScroll.fragment;
|
||||
break;
|
||||
}
|
||||
|
||||
this._register(_contributionProvider.onContributionsChanged(() => {
|
||||
this._register(contributionProvider.onContributionsChanged(() => {
|
||||
setTimeout(() => this.refresh(true), 0);
|
||||
}));
|
||||
|
||||
@@ -122,26 +136,26 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
}));
|
||||
}
|
||||
|
||||
this._register(this._webviewPanel.webview.onDidReceiveMessage((e: FromWebviewMessage.Type) => {
|
||||
if (e.source !== this._resource.toString()) {
|
||||
this._register(this.#webviewPanel.webview.onDidReceiveMessage((e: FromWebviewMessage.Type) => {
|
||||
if (e.source !== this.#resource.toString()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.type) {
|
||||
case 'cacheImageSizes':
|
||||
this._imageInfo = e.imageData;
|
||||
this.#imageInfo = e.imageData;
|
||||
break;
|
||||
|
||||
case 'revealLine':
|
||||
this._onDidScrollPreview(e.line);
|
||||
this.#onDidScrollPreview(e.line);
|
||||
break;
|
||||
|
||||
case 'didClick':
|
||||
this._onDidClickPreview(e.line);
|
||||
this.#onDidClickPreview(e.line);
|
||||
break;
|
||||
|
||||
case 'openLink':
|
||||
this._onDidClickPreviewLink(e.href);
|
||||
this.#onDidClickPreviewLink(e.href);
|
||||
break;
|
||||
|
||||
case 'showPreviewSecuritySelector':
|
||||
@@ -159,29 +173,29 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
this._disposeCts.cancel();
|
||||
this.#disposeCts.cancel();
|
||||
|
||||
super.dispose();
|
||||
|
||||
this._disposed = true;
|
||||
this.#disposed = true;
|
||||
|
||||
clearTimeout(this._throttleTimer);
|
||||
for (const entry of this._fileWatchersBySrc.values()) {
|
||||
clearTimeout(this.#throttleTimer);
|
||||
for (const entry of this.#fileWatchersBySrc.values()) {
|
||||
entry.dispose();
|
||||
}
|
||||
this._fileWatchersBySrc.clear();
|
||||
this.#fileWatchersBySrc.clear();
|
||||
}
|
||||
|
||||
public get resource(): vscode.Uri {
|
||||
return this._resource;
|
||||
return this.#resource;
|
||||
}
|
||||
|
||||
public get state() {
|
||||
return {
|
||||
resource: this._resource.toString(),
|
||||
line: this._line,
|
||||
fragment: this._scrollToFragment,
|
||||
...this._delegate.getAdditionalState(),
|
||||
resource: this.#resource.toString(),
|
||||
line: this.#line,
|
||||
fragment: this.#scrollToFragment,
|
||||
...this.#delegate.getAdditionalState(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,79 +205,79 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
*/
|
||||
public refresh(forceUpdate: boolean = false) {
|
||||
// Schedule update if none is pending
|
||||
if (!this._throttleTimer) {
|
||||
if (this._firstUpdate) {
|
||||
this._updatePreview(true);
|
||||
if (!this.#throttleTimer) {
|
||||
if (this.#firstUpdate) {
|
||||
this.#updatePreview(true);
|
||||
} else {
|
||||
this._throttleTimer = setTimeout(() => this._updatePreview(forceUpdate), this._delay);
|
||||
this.#throttleTimer = setTimeout(() => this.#updatePreview(forceUpdate), this.#delay);
|
||||
}
|
||||
}
|
||||
|
||||
this._firstUpdate = false;
|
||||
this.#firstUpdate = false;
|
||||
}
|
||||
|
||||
|
||||
public isPreviewOf(resource: vscode.Uri): boolean {
|
||||
return areUrisEqual(this._resource, resource);
|
||||
return areUrisEqual(this.#resource, resource);
|
||||
}
|
||||
|
||||
public postMessage(msg: ToWebviewMessage.Type) {
|
||||
if (!this._disposed) {
|
||||
this._webviewPanel.webview.postMessage(msg);
|
||||
if (!this.#disposed) {
|
||||
this.#webviewPanel.webview.postMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollTo(topLine: number) {
|
||||
if (this._disposed) {
|
||||
if (this.#disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isScrolling) {
|
||||
this._isScrolling = false;
|
||||
if (this.#isScrolling) {
|
||||
this.#isScrolling = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._logger.trace('MarkdownPreview', 'updateForView', { markdownFile: this._resource });
|
||||
this._line = topLine;
|
||||
this.#logger.trace('MarkdownPreview', 'updateForView', { markdownFile: this.#resource });
|
||||
this.#line = topLine;
|
||||
this.postMessage({
|
||||
type: 'updateView',
|
||||
line: topLine,
|
||||
source: this._resource.toString()
|
||||
source: this.#resource.toString()
|
||||
});
|
||||
}
|
||||
|
||||
private async _updatePreview(forceUpdate?: boolean): Promise<void> {
|
||||
clearTimeout(this._throttleTimer);
|
||||
this._throttleTimer = undefined;
|
||||
async #updatePreview(forceUpdate?: boolean): Promise<void> {
|
||||
clearTimeout(this.#throttleTimer);
|
||||
this.#throttleTimer = undefined;
|
||||
|
||||
if (this._disposed) {
|
||||
if (this.#disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let document: vscode.TextDocument;
|
||||
try {
|
||||
document = await vscode.workspace.openTextDocument(this._resource);
|
||||
document = await vscode.workspace.openTextDocument(this.#resource);
|
||||
} catch {
|
||||
if (!this._disposed) {
|
||||
await this._showFileNotFoundError();
|
||||
if (!this.#disposed) {
|
||||
await this.#showFileNotFoundError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._disposed) {
|
||||
if (this.#disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingVersion = new PreviewDocumentVersion(document);
|
||||
if (!forceUpdate && this._currentVersion?.equals(pendingVersion)) {
|
||||
if (this._line) {
|
||||
this.scrollTo(this._line);
|
||||
if (!forceUpdate && this.#currentVersion?.equals(pendingVersion)) {
|
||||
if (this.#line) {
|
||||
this.scrollTo(this.#line);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldReloadPage = forceUpdate || !this._currentVersion || this._currentVersion.resource.toString() !== pendingVersion.resource.toString() || !this._webviewPanel.visible;
|
||||
this._currentVersion = pendingVersion;
|
||||
const shouldReloadPage = forceUpdate || !this.#currentVersion || this.#currentVersion.resource.toString() !== pendingVersion.resource.toString() || !this.#webviewPanel.visible;
|
||||
this.#currentVersion = pendingVersion;
|
||||
|
||||
let selectedLine: number | undefined = undefined;
|
||||
for (const editor of vscode.window.visibleTextEditors) {
|
||||
@@ -274,21 +288,21 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
}
|
||||
|
||||
const content = await (shouldReloadPage
|
||||
? this._contentProvider.renderDocument(document, this, this._previewConfigurations, this._line, selectedLine, this.state, this._imageInfo, this._disposeCts.token)
|
||||
: this._contentProvider.renderBody(document, this));
|
||||
? this.#contentProvider.renderDocument(document, this, this.#previewConfigurations, this.#line, selectedLine, this.state, this.#imageInfo, this.#disposeCts.token)
|
||||
: this.#contentProvider.renderBody(document, this));
|
||||
|
||||
// Another call to `doUpdate` may have happened.
|
||||
// Make sure we are still updating for the correct document
|
||||
if (this._currentVersion?.equals(pendingVersion)) {
|
||||
this._updateWebviewContent(content.html, shouldReloadPage);
|
||||
this._updateImageWatchers(content.containingImages);
|
||||
if (this.#currentVersion?.equals(pendingVersion)) {
|
||||
this.#updateWebviewContent(content.html, shouldReloadPage);
|
||||
this.#updateImageWatchers(content.containingImages);
|
||||
}
|
||||
}
|
||||
|
||||
private _onDidScrollPreview(line: number) {
|
||||
this._line = line;
|
||||
this._onScrollEmitter.fire({ line: this._line, uri: this._resource });
|
||||
const config = this._previewConfigurations.loadAndCacheConfiguration(this._resource);
|
||||
#onDidScrollPreview(line: number) {
|
||||
this.#line = line;
|
||||
this.#onScrollEmitter.fire({ line: this.#line, uri: this.#resource });
|
||||
const config = this.#previewConfigurations.loadAndCacheConfiguration(this.#resource);
|
||||
if (!config.scrollEditorWithPreview) {
|
||||
return;
|
||||
}
|
||||
@@ -298,12 +312,12 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._isScrolling = true;
|
||||
this.#isScrolling = true;
|
||||
scrollEditorToLine(line, editor);
|
||||
}
|
||||
}
|
||||
|
||||
private async _onDidClickPreview(line: number): Promise<void> {
|
||||
async #onDidClickPreview(line: number): Promise<void> {
|
||||
// fix #82457, find currently opened but unfocused source tab
|
||||
await vscode.commands.executeCommand('markdown.showSource');
|
||||
|
||||
@@ -322,97 +336,97 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
await vscode.workspace.openTextDocument(this._resource)
|
||||
await vscode.workspace.openTextDocument(this.#resource)
|
||||
.then(vscode.window.showTextDocument)
|
||||
.then((editor) => {
|
||||
revealLineInEditor(editor);
|
||||
}, () => {
|
||||
vscode.window.showErrorMessage(vscode.l10n.t('Could not open {0}', this._resource.toString()));
|
||||
vscode.window.showErrorMessage(vscode.l10n.t('Could not open {0}', this.#resource.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
private async _showFileNotFoundError() {
|
||||
this._webviewPanel.webview.html = this._contentProvider.renderFileNotFoundDocument(this._resource);
|
||||
async #showFileNotFoundError() {
|
||||
this.#webviewPanel.webview.html = this.#contentProvider.renderFileNotFoundDocument(this.#resource);
|
||||
}
|
||||
|
||||
private _updateWebviewContent(html: string, reloadPage: boolean): void {
|
||||
if (this._disposed) {
|
||||
#updateWebviewContent(html: string, reloadPage: boolean): void {
|
||||
if (this.#disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._delegate.getTitle) {
|
||||
this._webviewPanel.title = this._delegate.getTitle(this._resource);
|
||||
if (this.#delegate.getTitle) {
|
||||
this.#webviewPanel.title = this.#delegate.getTitle(this.#resource);
|
||||
}
|
||||
this._webviewPanel.webview.options = this._getWebviewOptions();
|
||||
this.#webviewPanel.webview.options = this.#getWebviewOptions();
|
||||
|
||||
if (reloadPage) {
|
||||
this._webviewPanel.webview.html = html;
|
||||
this.#webviewPanel.webview.html = html;
|
||||
} else {
|
||||
this.postMessage({
|
||||
type: 'updateContent',
|
||||
content: html,
|
||||
source: this._resource.toString(),
|
||||
source: this.#resource.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _updateImageWatchers(srcs: Set<string>) {
|
||||
#updateImageWatchers(srcs: Set<string>) {
|
||||
// Delete stale file watchers.
|
||||
for (const [src, watcher] of this._fileWatchersBySrc) {
|
||||
for (const [src, watcher] of this.#fileWatchersBySrc) {
|
||||
if (!srcs.has(src)) {
|
||||
watcher.dispose();
|
||||
this._fileWatchersBySrc.delete(src);
|
||||
this.#fileWatchersBySrc.delete(src);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new file watchers.
|
||||
const root = vscode.Uri.joinPath(this._resource, '../');
|
||||
const root = vscode.Uri.joinPath(this.#resource, '../');
|
||||
for (const src of srcs) {
|
||||
const uri = urlToUri(src, root);
|
||||
if (uri && !MarkdownPreview._unwatchedImageSchemes.has(uri.scheme) && !this._fileWatchersBySrc.has(src)) {
|
||||
if (uri && !MarkdownPreview.#unwatchedImageSchemes.has(uri.scheme) && !this.#fileWatchersBySrc.has(src)) {
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'));
|
||||
watcher.onDidChange(() => {
|
||||
this.refresh(true);
|
||||
});
|
||||
this._fileWatchersBySrc.set(src, watcher);
|
||||
this.#fileWatchersBySrc.set(src, watcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getWebviewOptions(): vscode.WebviewOptions {
|
||||
#getWebviewOptions(): vscode.WebviewOptions {
|
||||
return {
|
||||
enableScripts: true,
|
||||
enableForms: false,
|
||||
localResourceRoots: this._getLocalResourceRoots()
|
||||
localResourceRoots: this.#getLocalResourceRoots()
|
||||
};
|
||||
}
|
||||
|
||||
private _getLocalResourceRoots(): ReadonlyArray<vscode.Uri> {
|
||||
const baseRoots = Array.from(this._contributionProvider.contributions.previewResourceRoots);
|
||||
#getLocalResourceRoots(): ReadonlyArray<vscode.Uri> {
|
||||
const baseRoots = Array.from(this.#contributionProvider.contributions.previewResourceRoots);
|
||||
|
||||
const folder = vscode.workspace.getWorkspaceFolder(this._resource);
|
||||
const folder = vscode.workspace.getWorkspaceFolder(this.#resource);
|
||||
if (folder) {
|
||||
const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri);
|
||||
if (workspaceRoots) {
|
||||
baseRoots.push(...workspaceRoots);
|
||||
}
|
||||
} else {
|
||||
baseRoots.push(uri.Utils.dirname(this._resource));
|
||||
baseRoots.push(uri.Utils.dirname(this.#resource));
|
||||
}
|
||||
|
||||
return baseRoots;
|
||||
}
|
||||
|
||||
private async _onDidClickPreviewLink(href: string) {
|
||||
async #onDidClickPreviewLink(href: string) {
|
||||
const config = vscode.workspace.getConfiguration('markdown', this.resource);
|
||||
const openLinks = config.get<string>('preview.openMarkdownLinks', 'inPreview');
|
||||
if (openLinks === 'inPreview') {
|
||||
const resolved = await this._opener.resolveDocumentLink(href, this.resource);
|
||||
const resolved = await this.#opener.resolveDocumentLink(href, this.resource);
|
||||
if (resolved.kind === 'file') {
|
||||
try {
|
||||
const doc = await vscode.workspace.openTextDocument(vscode.Uri.from(resolved.uri));
|
||||
if (isMarkdownFile(doc)) {
|
||||
return this._delegate.openPreviewLinkToMarkdownFile(doc.uri, resolved.fragment ? decodeURIComponent(resolved.fragment) : undefined);
|
||||
return this.#delegate.openPreviewLinkToMarkdownFile(doc.uri, resolved.fragment ? decodeURIComponent(resolved.fragment) : undefined);
|
||||
}
|
||||
} catch {
|
||||
// Noop
|
||||
@@ -420,21 +434,21 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
return this._opener.openDocumentLink(href, this.resource);
|
||||
return this.#opener.openDocumentLink(href, this.resource);
|
||||
}
|
||||
|
||||
//#region WebviewResourceProvider
|
||||
|
||||
asWebviewUri(resource: vscode.Uri) {
|
||||
return this._webviewPanel.webview.asWebviewUri(resource);
|
||||
return this.#webviewPanel.webview.asWebviewUri(resource);
|
||||
}
|
||||
|
||||
get cspSource() {
|
||||
return [
|
||||
this._webviewPanel.webview.cspSource,
|
||||
this.#webviewPanel.webview.cspSource,
|
||||
|
||||
// On web, we also need to allow loading of resources from contributed extensions
|
||||
...this._contributionProvider.contributions.previewResourceRoots
|
||||
...this.#contributionProvider.contributions.previewResourceRoots
|
||||
.filter(root => root.scheme === 'http' || root.scheme === 'https')
|
||||
.map(root => {
|
||||
const dirRoot = root.path.endsWith('/') ? root : root.with({ path: root.path + '/' });
|
||||
@@ -484,13 +498,16 @@ export class StaticMarkdownPreview extends Disposable implements IManagedMarkdow
|
||||
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, opener, scrollLine);
|
||||
}
|
||||
|
||||
private readonly _preview: MarkdownPreview;
|
||||
readonly #preview: MarkdownPreview;
|
||||
|
||||
readonly #webviewPanel: vscode.WebviewPanel;
|
||||
readonly #previewConfigurations: MarkdownPreviewConfigurationManager;
|
||||
|
||||
private constructor(
|
||||
private readonly _webviewPanel: vscode.WebviewPanel,
|
||||
webviewPanel: vscode.WebviewPanel,
|
||||
resource: vscode.Uri,
|
||||
contentProvider: MdDocumentRenderer,
|
||||
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
topmostLineMonitor: TopmostLineMonitor,
|
||||
logger: ILogger,
|
||||
contributionProvider: MarkdownContributionProvider,
|
||||
@@ -498,52 +515,56 @@ export class StaticMarkdownPreview extends Disposable implements IManagedMarkdow
|
||||
scrollLine?: number,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.#webviewPanel = webviewPanel;
|
||||
this.#previewConfigurations = previewConfigurations;
|
||||
|
||||
const topScrollLocation = scrollLine ? new StartingScrollLine(scrollLine) : undefined;
|
||||
this._preview = this._register(new MarkdownPreview(this._webviewPanel, resource, topScrollLocation, {
|
||||
this.#preview = this._register(new MarkdownPreview(this.#webviewPanel, resource, topScrollLocation, {
|
||||
getAdditionalState: () => { return {}; },
|
||||
openPreviewLinkToMarkdownFile: (markdownLink, fragment) => {
|
||||
return vscode.commands.executeCommand('vscode.openWith', markdownLink.with({
|
||||
fragment
|
||||
}), StaticMarkdownPreview.customEditorViewType, this._webviewPanel.viewColumn);
|
||||
}), StaticMarkdownPreview.customEditorViewType, this.#webviewPanel.viewColumn);
|
||||
}
|
||||
}, contentProvider, _previewConfigurations, logger, contributionProvider, opener));
|
||||
}, contentProvider, previewConfigurations, logger, contributionProvider, opener));
|
||||
|
||||
this._register(this._webviewPanel.onDidDispose(() => {
|
||||
this._register(this.#webviewPanel.onDidDispose(() => {
|
||||
this.dispose();
|
||||
}));
|
||||
|
||||
this._register(this._webviewPanel.onDidChangeViewState(e => {
|
||||
this._onDidChangeViewState.fire(e);
|
||||
this._register(this.#webviewPanel.onDidChangeViewState(e => {
|
||||
this.#onDidChangeViewState.fire(e);
|
||||
}));
|
||||
|
||||
this._register(this._preview.onScroll((scrollInfo) => {
|
||||
this._register(this.#preview.onScroll((scrollInfo) => {
|
||||
topmostLineMonitor.setPreviousStaticEditorLine(scrollInfo);
|
||||
}));
|
||||
|
||||
this._register(topmostLineMonitor.onDidChanged(event => {
|
||||
if (this._preview.isPreviewOf(event.resource)) {
|
||||
this._preview.scrollTo(event.line);
|
||||
if (this.#preview.isPreviewOf(event.resource)) {
|
||||
this.#preview.scrollTo(event.line);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
copyImage(id: string) {
|
||||
this._webviewPanel.reveal();
|
||||
this._preview.postMessage({
|
||||
this.#webviewPanel.reveal();
|
||||
this.#preview.postMessage({
|
||||
type: 'copyImage',
|
||||
source: this.resource.toString(),
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _onDispose = this._register(new vscode.EventEmitter<void>());
|
||||
public readonly onDispose = this._onDispose.event;
|
||||
readonly #onDispose = this._register(new vscode.EventEmitter<void>());
|
||||
public readonly onDispose = this.#onDispose.event;
|
||||
|
||||
private readonly _onDidChangeViewState = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
|
||||
public readonly onDidChangeViewState = this._onDidChangeViewState.event;
|
||||
readonly #onDidChangeViewState = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
|
||||
public readonly onDidChangeViewState = this.#onDidChangeViewState.event;
|
||||
|
||||
override dispose() {
|
||||
this._onDispose.fire();
|
||||
this.#onDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -556,21 +577,21 @@ export class StaticMarkdownPreview extends Disposable implements IManagedMarkdow
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
this._preview.refresh(true);
|
||||
this.#preview.refresh(true);
|
||||
}
|
||||
|
||||
public updateConfiguration() {
|
||||
if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) {
|
||||
if (this.#previewConfigurations.hasConfigurationChanged(this.#preview.resource)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public get resource() {
|
||||
return this._preview.resource;
|
||||
return this.#preview.resource;
|
||||
}
|
||||
|
||||
public get resourceColumn() {
|
||||
return this._webviewPanel.viewColumn || vscode.ViewColumn.One;
|
||||
return this.#webviewPanel.viewColumn || vscode.ViewColumn.One;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,11 +606,11 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
|
||||
|
||||
public static readonly viewType = 'markdown.preview';
|
||||
|
||||
private readonly _resourceColumn: vscode.ViewColumn;
|
||||
private _locked: boolean;
|
||||
readonly #resourceColumn: vscode.ViewColumn;
|
||||
#locked: boolean;
|
||||
|
||||
private readonly _webviewPanel: vscode.WebviewPanel;
|
||||
private _preview: MarkdownPreview;
|
||||
readonly #webviewPanel: vscode.WebviewPanel;
|
||||
#preview: MarkdownPreview;
|
||||
|
||||
public static revive(
|
||||
input: DynamicPreviewInput,
|
||||
@@ -619,7 +640,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
|
||||
): DynamicMarkdownPreview {
|
||||
const webview = vscode.window.createWebviewPanel(
|
||||
DynamicMarkdownPreview.viewType,
|
||||
DynamicMarkdownPreview._getPreviewTitle(input.resource, input.locked),
|
||||
DynamicMarkdownPreview.#getPreviewTitle(input.resource, input.locked),
|
||||
previewColumn, { enableFindWidget: true, });
|
||||
|
||||
webview.iconPath = contentProvider.iconPath;
|
||||
@@ -628,43 +649,57 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
|
||||
contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, opener);
|
||||
}
|
||||
|
||||
readonly #contentProvider: MdDocumentRenderer;
|
||||
readonly #previewConfigurations: MarkdownPreviewConfigurationManager;
|
||||
readonly #logger: ILogger;
|
||||
readonly #topmostLineMonitor: TopmostLineMonitor;
|
||||
readonly #contributionProvider: MarkdownContributionProvider;
|
||||
readonly #opener: MdLinkOpener;
|
||||
|
||||
private constructor(
|
||||
webview: vscode.WebviewPanel,
|
||||
input: DynamicPreviewInput,
|
||||
private readonly _contentProvider: MdDocumentRenderer,
|
||||
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
private readonly _logger: ILogger,
|
||||
private readonly _topmostLineMonitor: TopmostLineMonitor,
|
||||
private readonly _contributionProvider: MarkdownContributionProvider,
|
||||
private readonly _opener: MdLinkOpener,
|
||||
contentProvider: MdDocumentRenderer,
|
||||
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
logger: ILogger,
|
||||
topmostLineMonitor: TopmostLineMonitor,
|
||||
contributionProvider: MarkdownContributionProvider,
|
||||
opener: MdLinkOpener,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._webviewPanel = webview;
|
||||
this.#contentProvider = contentProvider;
|
||||
this.#previewConfigurations = previewConfigurations;
|
||||
this.#logger = logger;
|
||||
this.#topmostLineMonitor = topmostLineMonitor;
|
||||
this.#contributionProvider = contributionProvider;
|
||||
this.#opener = opener;
|
||||
|
||||
this._resourceColumn = input.resourceColumn;
|
||||
this._locked = input.locked;
|
||||
this.#webviewPanel = webview;
|
||||
|
||||
this._preview = this._createPreview(input.resource, typeof input.line === 'number' ? new StartingScrollLine(input.line) : undefined);
|
||||
this.#resourceColumn = input.resourceColumn;
|
||||
this.#locked = input.locked;
|
||||
|
||||
this.#preview = this.#createPreview(input.resource, typeof input.line === 'number' ? new StartingScrollLine(input.line) : undefined);
|
||||
|
||||
this._register(webview.onDidDispose(() => { this.dispose(); }));
|
||||
|
||||
this._register(this._webviewPanel.onDidChangeViewState(e => {
|
||||
this._onDidChangeViewStateEmitter.fire(e);
|
||||
this._register(this.#webviewPanel.onDidChangeViewState(e => {
|
||||
this.#onDidChangeViewStateEmitter.fire(e);
|
||||
}));
|
||||
|
||||
this._register(this._topmostLineMonitor.onDidChanged(event => {
|
||||
if (this._preview.isPreviewOf(event.resource)) {
|
||||
this._preview.scrollTo(event.line);
|
||||
this._register(this.#topmostLineMonitor.onDidChanged(event => {
|
||||
if (this.#preview.isPreviewOf(event.resource)) {
|
||||
this.#preview.scrollTo(event.line);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(vscode.window.onDidChangeTextEditorSelection(event => {
|
||||
if (this._preview.isPreviewOf(event.textEditor.document.uri)) {
|
||||
this._preview.postMessage({
|
||||
if (this.#preview.isPreviewOf(event.textEditor.document.uri)) {
|
||||
this.#preview.postMessage({
|
||||
type: 'onDidChangeTextEditorSelection',
|
||||
line: event.selections[0].active.line,
|
||||
source: this._preview.resource.toString()
|
||||
source: this.#preview.resource.toString()
|
||||
});
|
||||
}
|
||||
}));
|
||||
@@ -675,7 +710,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMarkdownFile(editor.document) && !this._locked && !this._preview.isPreviewOf(editor.document.uri)) {
|
||||
if (isMarkdownFile(editor.document) && !this.#locked && !this.#preview.isPreviewOf(editor.document.uri)) {
|
||||
const line = getVisibleLine(editor);
|
||||
this.update(editor.document.uri, line ? new StartingScrollLine(line) : undefined);
|
||||
}
|
||||
@@ -683,56 +718,56 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
|
||||
}
|
||||
|
||||
copyImage(id: string) {
|
||||
this._webviewPanel.reveal();
|
||||
this._preview.postMessage({
|
||||
this.#webviewPanel.reveal();
|
||||
this.#preview.postMessage({
|
||||
type: 'copyImage',
|
||||
source: this.resource.toString(),
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter<void>());
|
||||
public readonly onDispose = this._onDisposeEmitter.event;
|
||||
readonly #onDisposeEmitter = this._register(new vscode.EventEmitter<void>());
|
||||
public readonly onDispose = this.#onDisposeEmitter.event;
|
||||
|
||||
private readonly _onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
|
||||
public readonly onDidChangeViewState = this._onDidChangeViewStateEmitter.event;
|
||||
readonly #onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
|
||||
public readonly onDidChangeViewState = this.#onDidChangeViewStateEmitter.event;
|
||||
|
||||
override dispose() {
|
||||
this._preview.dispose();
|
||||
this._webviewPanel.dispose();
|
||||
this.#preview.dispose();
|
||||
this.#webviewPanel.dispose();
|
||||
|
||||
this._onDisposeEmitter.fire();
|
||||
this._onDisposeEmitter.dispose();
|
||||
this.#onDisposeEmitter.fire();
|
||||
this.#onDisposeEmitter.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public get resource() {
|
||||
return this._preview.resource;
|
||||
return this.#preview.resource;
|
||||
}
|
||||
|
||||
public get resourceColumn() {
|
||||
return this._resourceColumn;
|
||||
return this.#resourceColumn;
|
||||
}
|
||||
|
||||
public reveal(viewColumn: vscode.ViewColumn) {
|
||||
this._webviewPanel.reveal(viewColumn);
|
||||
this.#webviewPanel.reveal(viewColumn);
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
this._preview.refresh(true);
|
||||
this.#preview.refresh(true);
|
||||
}
|
||||
|
||||
public updateConfiguration() {
|
||||
if (this._previewConfigurations.hasConfigurationChanged(this._preview.resource)) {
|
||||
if (this.#previewConfigurations.hasConfigurationChanged(this.#preview.resource)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public update(newResource: vscode.Uri, scrollLocation?: StartingScrollLocation) {
|
||||
if (this._preview.isPreviewOf(newResource)) {
|
||||
if (this.#preview.isPreviewOf(newResource)) {
|
||||
switch (scrollLocation?.type) {
|
||||
case 'line':
|
||||
this._preview.scrollTo(scrollLocation.line);
|
||||
this.#preview.scrollTo(scrollLocation.line);
|
||||
return;
|
||||
|
||||
case 'fragment':
|
||||
@@ -744,16 +779,16 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
|
||||
}
|
||||
}
|
||||
|
||||
this._preview.dispose();
|
||||
this._preview = this._createPreview(newResource, scrollLocation);
|
||||
this.#preview.dispose();
|
||||
this.#preview = this.#createPreview(newResource, scrollLocation);
|
||||
}
|
||||
|
||||
public toggleLock() {
|
||||
this._locked = !this._locked;
|
||||
this._webviewPanel.title = DynamicMarkdownPreview._getPreviewTitle(this._preview.resource, this._locked);
|
||||
this.#locked = !this.#locked;
|
||||
this.#webviewPanel.title = DynamicMarkdownPreview.#getPreviewTitle(this.#preview.resource, this.#locked);
|
||||
}
|
||||
|
||||
private static _getPreviewTitle(resource: vscode.Uri, locked: boolean): string {
|
||||
static #getPreviewTitle(resource: vscode.Uri, locked: boolean): string {
|
||||
const resourceLabel = uri.Utils.basename(resource);
|
||||
return locked
|
||||
? vscode.l10n.t('[Preview] {0}', resourceLabel)
|
||||
@@ -761,7 +796,7 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
|
||||
}
|
||||
|
||||
public get position(): vscode.ViewColumn | undefined {
|
||||
return this._webviewPanel.viewColumn;
|
||||
return this.#webviewPanel.viewColumn;
|
||||
}
|
||||
|
||||
public matchesResource(
|
||||
@@ -773,34 +808,34 @@ export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdo
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._locked) {
|
||||
return otherLocked && this._preview.isPreviewOf(otherResource);
|
||||
if (this.#locked) {
|
||||
return otherLocked && this.#preview.isPreviewOf(otherResource);
|
||||
} else {
|
||||
return !otherLocked;
|
||||
}
|
||||
}
|
||||
|
||||
public matches(otherPreview: DynamicMarkdownPreview): boolean {
|
||||
return this.matchesResource(otherPreview._preview.resource, otherPreview.position, otherPreview._locked);
|
||||
return this.matchesResource(otherPreview.#preview.resource, otherPreview.position, otherPreview.#locked);
|
||||
}
|
||||
|
||||
private _createPreview(resource: vscode.Uri, startingScroll?: StartingScrollLocation): MarkdownPreview {
|
||||
return new MarkdownPreview(this._webviewPanel, resource, startingScroll, {
|
||||
getTitle: (resource) => DynamicMarkdownPreview._getPreviewTitle(resource, this._locked),
|
||||
#createPreview(resource: vscode.Uri, startingScroll?: StartingScrollLocation): MarkdownPreview {
|
||||
return new MarkdownPreview(this.#webviewPanel, resource, startingScroll, {
|
||||
getTitle: (resource) => DynamicMarkdownPreview.#getPreviewTitle(resource, this.#locked),
|
||||
getAdditionalState: () => {
|
||||
return {
|
||||
resourceColumn: this.resourceColumn,
|
||||
locked: this._locked,
|
||||
locked: this.#locked,
|
||||
};
|
||||
},
|
||||
openPreviewLinkToMarkdownFile: (link: vscode.Uri, fragment?: string) => {
|
||||
this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined);
|
||||
}
|
||||
},
|
||||
this._contentProvider,
|
||||
this._previewConfigurations,
|
||||
this._logger,
|
||||
this._contributionProvider,
|
||||
this._opener);
|
||||
this.#contentProvider,
|
||||
this.#previewConfigurations,
|
||||
this.#logger,
|
||||
this.#contributionProvider,
|
||||
this.#opener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,24 +73,24 @@ export class MarkdownPreviewConfiguration {
|
||||
}
|
||||
|
||||
export class MarkdownPreviewConfigurationManager {
|
||||
private readonly _previewConfigurationsForWorkspaces = new Map<string, MarkdownPreviewConfiguration>();
|
||||
readonly #previewConfigurationsForWorkspaces = new Map<string, MarkdownPreviewConfiguration>();
|
||||
|
||||
public loadAndCacheConfiguration(
|
||||
resource: vscode.Uri
|
||||
): MarkdownPreviewConfiguration {
|
||||
const config = MarkdownPreviewConfiguration.getForResource(resource);
|
||||
this._previewConfigurationsForWorkspaces.set(this._getKey(resource), config);
|
||||
this.#previewConfigurationsForWorkspaces.set(this.#getKey(resource), config);
|
||||
return config;
|
||||
}
|
||||
|
||||
public hasConfigurationChanged(resource: vscode.Uri): boolean {
|
||||
const key = this._getKey(resource);
|
||||
const currentConfig = this._previewConfigurationsForWorkspaces.get(key);
|
||||
const key = this.#getKey(resource);
|
||||
const currentConfig = this.#previewConfigurationsForWorkspaces.get(key);
|
||||
const newConfig = MarkdownPreviewConfiguration.getForResource(resource);
|
||||
return !currentConfig?.isEqualTo(newConfig);
|
||||
}
|
||||
|
||||
private _getKey(
|
||||
#getKey(
|
||||
resource: vscode.Uri
|
||||
): string {
|
||||
const folder = vscode.workspace.getWorkspaceFolder(resource);
|
||||
|
||||
@@ -24,23 +24,23 @@ export interface DynamicPreviewSettings {
|
||||
|
||||
class PreviewStore<T extends IManagedMarkdownPreview> extends Disposable {
|
||||
|
||||
private readonly _previews = new Set<T>();
|
||||
readonly #previews = new Set<T>();
|
||||
|
||||
public override dispose(): void {
|
||||
super.dispose();
|
||||
for (const preview of this._previews) {
|
||||
for (const preview of this.#previews) {
|
||||
preview.dispose();
|
||||
}
|
||||
this._previews.clear();
|
||||
this.#previews.clear();
|
||||
}
|
||||
|
||||
[Symbol.iterator](): Iterator<T> {
|
||||
return this._previews[Symbol.iterator]();
|
||||
return this.#previews[Symbol.iterator]();
|
||||
}
|
||||
|
||||
public get(resource: vscode.Uri, previewSettings: DynamicPreviewSettings): T | undefined {
|
||||
const previewColumn = this._resolvePreviewColumn(previewSettings);
|
||||
for (const preview of this._previews) {
|
||||
const previewColumn = this.#resolvePreviewColumn(previewSettings);
|
||||
for (const preview of this.#previews) {
|
||||
if (preview.matchesResource(resource, previewColumn, previewSettings.locked)) {
|
||||
return preview;
|
||||
}
|
||||
@@ -49,14 +49,14 @@ class PreviewStore<T extends IManagedMarkdownPreview> extends Disposable {
|
||||
}
|
||||
|
||||
public add(preview: T) {
|
||||
this._previews.add(preview);
|
||||
this.#previews.add(preview);
|
||||
}
|
||||
|
||||
public delete(preview: T) {
|
||||
this._previews.delete(preview);
|
||||
this.#previews.delete(preview);
|
||||
}
|
||||
|
||||
private _resolvePreviewColumn(previewSettings: DynamicPreviewSettings): vscode.ViewColumn | undefined {
|
||||
#resolvePreviewColumn(previewSettings: DynamicPreviewSettings): vscode.ViewColumn | undefined {
|
||||
if (previewSettings.previewColumn === vscode.ViewColumn.Active) {
|
||||
return vscode.window.tabGroups.activeTabGroup.viewColumn;
|
||||
}
|
||||
@@ -71,22 +71,32 @@ class PreviewStore<T extends IManagedMarkdownPreview> extends Disposable {
|
||||
|
||||
export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider {
|
||||
|
||||
private readonly _topmostLineMonitor = new TopmostLineMonitor();
|
||||
private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager();
|
||||
readonly #topmostLineMonitor = new TopmostLineMonitor();
|
||||
readonly #previewConfigurations = new MarkdownPreviewConfigurationManager();
|
||||
|
||||
private readonly _dynamicPreviews = this._register(new PreviewStore<DynamicMarkdownPreview>());
|
||||
private readonly _staticPreviews = this._register(new PreviewStore<StaticMarkdownPreview>());
|
||||
readonly #dynamicPreviews = this._register(new PreviewStore<DynamicMarkdownPreview>());
|
||||
readonly #staticPreviews = this._register(new PreviewStore<StaticMarkdownPreview>());
|
||||
|
||||
private _activePreview: IManagedMarkdownPreview | undefined = undefined;
|
||||
#activePreview: IManagedMarkdownPreview | undefined = undefined;
|
||||
|
||||
readonly #contentProvider: MdDocumentRenderer;
|
||||
readonly #logger: ILogger;
|
||||
readonly #contributions: MarkdownContributionProvider;
|
||||
readonly #opener: MdLinkOpener;
|
||||
|
||||
public constructor(
|
||||
private readonly _contentProvider: MdDocumentRenderer,
|
||||
private readonly _logger: ILogger,
|
||||
private readonly _contributions: MarkdownContributionProvider,
|
||||
private readonly _opener: MdLinkOpener,
|
||||
contentProvider: MdDocumentRenderer,
|
||||
logger: ILogger,
|
||||
contributions: MarkdownContributionProvider,
|
||||
opener: MdLinkOpener,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.#contentProvider = contentProvider;
|
||||
this.#logger = logger;
|
||||
this.#contributions = contributions;
|
||||
this.#opener = opener;
|
||||
|
||||
this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this));
|
||||
|
||||
this._register(vscode.window.registerCustomEditorProvider(StaticMarkdownPreview.customEditorViewType, this, {
|
||||
@@ -96,7 +106,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
this._register(vscode.window.onDidChangeActiveTextEditor(textEditor => {
|
||||
// When at a markdown file, apply existing scroll settings
|
||||
if (textEditor?.document && isMarkdownFile(textEditor.document)) {
|
||||
const line = this._topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri);
|
||||
const line = this.#topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri);
|
||||
if (typeof line === 'number') {
|
||||
scrollEditorToLine(line, textEditor);
|
||||
}
|
||||
@@ -105,19 +115,19 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
for (const preview of this._dynamicPreviews) {
|
||||
for (const preview of this.#dynamicPreviews) {
|
||||
preview.refresh();
|
||||
}
|
||||
for (const preview of this._staticPreviews) {
|
||||
for (const preview of this.#staticPreviews) {
|
||||
preview.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public updateConfiguration() {
|
||||
for (const preview of this._dynamicPreviews) {
|
||||
for (const preview of this.#dynamicPreviews) {
|
||||
preview.updateConfiguration();
|
||||
}
|
||||
for (const preview of this._staticPreviews) {
|
||||
for (const preview of this.#staticPreviews) {
|
||||
preview.updateConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -126,11 +136,11 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
resource: vscode.Uri,
|
||||
settings: DynamicPreviewSettings
|
||||
): void {
|
||||
let preview = this._dynamicPreviews.get(resource, settings);
|
||||
let preview = this.#dynamicPreviews.get(resource, settings);
|
||||
if (preview) {
|
||||
preview.reveal(settings.previewColumn);
|
||||
} else {
|
||||
preview = this._createNewDynamicPreview(resource, settings);
|
||||
preview = this.#createNewDynamicPreview(resource, settings);
|
||||
}
|
||||
|
||||
preview.update(
|
||||
@@ -140,15 +150,15 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
}
|
||||
|
||||
public get activePreviewResource() {
|
||||
return this._activePreview?.resource;
|
||||
return this.#activePreview?.resource;
|
||||
}
|
||||
|
||||
public get activePreviewResourceColumn() {
|
||||
return this._activePreview?.resourceColumn;
|
||||
return this.#activePreview?.resourceColumn;
|
||||
}
|
||||
|
||||
public findPreview(resource: vscode.Uri): IManagedMarkdownPreview | undefined {
|
||||
for (const preview of [...this._dynamicPreviews, ...this._staticPreviews]) {
|
||||
for (const preview of [...this.#dynamicPreviews, ...this.#staticPreviews]) {
|
||||
if (preview.resource.fsPath === resource.fsPath) {
|
||||
return preview;
|
||||
}
|
||||
@@ -157,12 +167,12 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
}
|
||||
|
||||
public toggleLock() {
|
||||
const preview = this._activePreview;
|
||||
const preview = this.#activePreview;
|
||||
if (preview instanceof DynamicMarkdownPreview) {
|
||||
preview.toggleLock();
|
||||
|
||||
// Close any previews that are now redundant, such as having two dynamic previews in the same editor group
|
||||
for (const otherPreview of this._dynamicPreviews) {
|
||||
for (const otherPreview of this.#dynamicPreviews) {
|
||||
if (otherPreview !== preview && preview.matches(otherPreview)) {
|
||||
otherPreview.dispose();
|
||||
}
|
||||
@@ -172,7 +182,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
|
||||
public openDocumentLink(linkText: string, fromResource: vscode.Uri) {
|
||||
const viewColumn = this.findPreview(fromResource)?.resourceColumn;
|
||||
return this._opener.openDocumentLink(linkText, fromResource, viewColumn);
|
||||
return this.#opener.openDocumentLink(linkText, fromResource, viewColumn);
|
||||
}
|
||||
|
||||
public async deserializeWebviewPanel(
|
||||
@@ -188,14 +198,14 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
const preview = DynamicMarkdownPreview.revive(
|
||||
{ resource, locked, line, resourceColumn },
|
||||
webview,
|
||||
this._contentProvider,
|
||||
this._previewConfigurations,
|
||||
this._logger,
|
||||
this._topmostLineMonitor,
|
||||
this._contributions,
|
||||
this._opener);
|
||||
this.#contentProvider,
|
||||
this.#previewConfigurations,
|
||||
this.#logger,
|
||||
this.#topmostLineMonitor,
|
||||
this.#contributions,
|
||||
this.#opener);
|
||||
|
||||
this._registerDynamicPreview(preview);
|
||||
this.#registerDynamicPreview(preview);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -237,23 +247,23 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
document: vscode.TextDocument,
|
||||
webview: vscode.WebviewPanel
|
||||
): Promise<void> {
|
||||
const lineNumber = this._topmostLineMonitor.getPreviousStaticTextEditorLineByUri(document.uri);
|
||||
const lineNumber = this.#topmostLineMonitor.getPreviousStaticTextEditorLineByUri(document.uri);
|
||||
const preview = StaticMarkdownPreview.revive(
|
||||
document.uri,
|
||||
webview,
|
||||
this._contentProvider,
|
||||
this._previewConfigurations,
|
||||
this._topmostLineMonitor,
|
||||
this._logger,
|
||||
this._contributions,
|
||||
this._opener,
|
||||
this.#contentProvider,
|
||||
this.#previewConfigurations,
|
||||
this.#topmostLineMonitor,
|
||||
this.#logger,
|
||||
this.#contributions,
|
||||
this.#opener,
|
||||
lineNumber
|
||||
);
|
||||
this._registerStaticPreview(preview);
|
||||
this._activePreview = preview;
|
||||
this.#registerStaticPreview(preview);
|
||||
this.#activePreview = preview;
|
||||
}
|
||||
|
||||
private _createNewDynamicPreview(
|
||||
#createNewDynamicPreview(
|
||||
resource: vscode.Uri,
|
||||
previewSettings: DynamicPreviewSettings
|
||||
): DynamicMarkdownPreview {
|
||||
@@ -267,52 +277,52 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
line: scrollLine,
|
||||
},
|
||||
previewSettings.previewColumn,
|
||||
this._contentProvider,
|
||||
this._previewConfigurations,
|
||||
this._logger,
|
||||
this._topmostLineMonitor,
|
||||
this._contributions,
|
||||
this._opener);
|
||||
this.#contentProvider,
|
||||
this.#previewConfigurations,
|
||||
this.#logger,
|
||||
this.#topmostLineMonitor,
|
||||
this.#contributions,
|
||||
this.#opener);
|
||||
|
||||
this._activePreview = preview;
|
||||
return this._registerDynamicPreview(preview);
|
||||
this.#activePreview = preview;
|
||||
return this.#registerDynamicPreview(preview);
|
||||
}
|
||||
|
||||
private _registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {
|
||||
this._dynamicPreviews.add(preview);
|
||||
#registerDynamicPreview(preview: DynamicMarkdownPreview): DynamicMarkdownPreview {
|
||||
this.#dynamicPreviews.add(preview);
|
||||
|
||||
preview.onDispose(() => {
|
||||
this._dynamicPreviews.delete(preview);
|
||||
this.#dynamicPreviews.delete(preview);
|
||||
});
|
||||
|
||||
this._trackActive(preview);
|
||||
this.#trackActive(preview);
|
||||
|
||||
preview.onDidChangeViewState(() => {
|
||||
// Remove other dynamic previews in our column
|
||||
disposeAll(Array.from(this._dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview)));
|
||||
disposeAll(Array.from(this.#dynamicPreviews).filter(otherPreview => preview !== otherPreview && preview.matches(otherPreview)));
|
||||
});
|
||||
return preview;
|
||||
}
|
||||
|
||||
private _registerStaticPreview(preview: StaticMarkdownPreview): StaticMarkdownPreview {
|
||||
this._staticPreviews.add(preview);
|
||||
#registerStaticPreview(preview: StaticMarkdownPreview): StaticMarkdownPreview {
|
||||
this.#staticPreviews.add(preview);
|
||||
|
||||
preview.onDispose(() => {
|
||||
this._staticPreviews.delete(preview);
|
||||
this.#staticPreviews.delete(preview);
|
||||
});
|
||||
|
||||
this._trackActive(preview);
|
||||
this.#trackActive(preview);
|
||||
return preview;
|
||||
}
|
||||
|
||||
private _trackActive(preview: IManagedMarkdownPreview): void {
|
||||
#trackActive(preview: IManagedMarkdownPreview): void {
|
||||
preview.onDidChangeViewState(({ webviewPanel }) => {
|
||||
this._activePreview = webviewPanel.active ? preview : undefined;
|
||||
this.#activePreview = webviewPanel.active ? preview : undefined;
|
||||
});
|
||||
|
||||
preview.onDispose(() => {
|
||||
if (this._activePreview === preview) {
|
||||
this._activePreview = undefined;
|
||||
if (this.#activePreview === preview) {
|
||||
this.#activePreview = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,31 +27,37 @@ export interface ContentSecurityPolicyArbiter {
|
||||
}
|
||||
|
||||
export class ExtensionContentSecurityPolicyArbiter implements ContentSecurityPolicyArbiter {
|
||||
private readonly _old_trusted_workspace_key = 'trusted_preview_workspace:';
|
||||
private readonly _security_level_key = 'preview_security_level:';
|
||||
private readonly _should_disable_security_warning_key = 'preview_should_show_security_warning:';
|
||||
readonly #old_trusted_workspace_key = 'trusted_preview_workspace:';
|
||||
readonly #security_level_key = 'preview_security_level:';
|
||||
readonly #should_disable_security_warning_key = 'preview_should_show_security_warning:';
|
||||
|
||||
readonly #globalState: vscode.Memento;
|
||||
readonly #workspaceState: vscode.Memento;
|
||||
|
||||
constructor(
|
||||
private readonly _globalState: vscode.Memento,
|
||||
private readonly _workspaceState: vscode.Memento
|
||||
) { }
|
||||
globalState: vscode.Memento,
|
||||
workspaceState: vscode.Memento
|
||||
) {
|
||||
this.#globalState = globalState;
|
||||
this.#workspaceState = workspaceState;
|
||||
}
|
||||
|
||||
public getSecurityLevelForResource(resource: vscode.Uri): MarkdownPreviewSecurityLevel {
|
||||
// Use new security level setting first
|
||||
const level = this._globalState.get<MarkdownPreviewSecurityLevel | undefined>(this._security_level_key + this._getRoot(resource), undefined);
|
||||
const level = this.#globalState.get<MarkdownPreviewSecurityLevel | undefined>(this.#security_level_key + this.#getRoot(resource), undefined);
|
||||
if (typeof level !== 'undefined') {
|
||||
return level;
|
||||
}
|
||||
|
||||
// Fallback to old trusted workspace setting
|
||||
if (this._globalState.get<boolean>(this._old_trusted_workspace_key + this._getRoot(resource), false)) {
|
||||
if (this.#globalState.get<boolean>(this.#old_trusted_workspace_key + this.#getRoot(resource), false)) {
|
||||
return MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent;
|
||||
}
|
||||
return MarkdownPreviewSecurityLevel.Strict;
|
||||
}
|
||||
|
||||
public setSecurityLevelForResource(resource: vscode.Uri, level: MarkdownPreviewSecurityLevel): Thenable<void> {
|
||||
return this._globalState.update(this._security_level_key + this._getRoot(resource), level);
|
||||
return this.#globalState.update(this.#security_level_key + this.#getRoot(resource), level);
|
||||
}
|
||||
|
||||
public shouldAllowSvgsForResource(resource: vscode.Uri) {
|
||||
@@ -60,14 +66,14 @@ export class ExtensionContentSecurityPolicyArbiter implements ContentSecurityPol
|
||||
}
|
||||
|
||||
public shouldDisableSecurityWarnings(): boolean {
|
||||
return this._workspaceState.get<boolean>(this._should_disable_security_warning_key, false);
|
||||
return this.#workspaceState.get<boolean>(this.#should_disable_security_warning_key, false);
|
||||
}
|
||||
|
||||
public setShouldDisableSecurityWarning(disabled: boolean): Thenable<void> {
|
||||
return this._workspaceState.update(this._should_disable_security_warning_key, disabled);
|
||||
return this.#workspaceState.update(this.#should_disable_security_warning_key, disabled);
|
||||
}
|
||||
|
||||
private _getRoot(resource: vscode.Uri): vscode.Uri {
|
||||
#getRoot(resource: vscode.Uri): vscode.Uri {
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
const folderForResource = vscode.workspace.getWorkspaceFolder(resource);
|
||||
if (folderForResource) {
|
||||
@@ -85,10 +91,16 @@ export class ExtensionContentSecurityPolicyArbiter implements ContentSecurityPol
|
||||
|
||||
export class PreviewSecuritySelector {
|
||||
|
||||
readonly #cspArbiter: ContentSecurityPolicyArbiter;
|
||||
readonly #webviewManager: MarkdownPreviewManager;
|
||||
|
||||
public constructor(
|
||||
private readonly _cspArbiter: ContentSecurityPolicyArbiter,
|
||||
private readonly _webviewManager: MarkdownPreviewManager
|
||||
) { }
|
||||
cspArbiter: ContentSecurityPolicyArbiter,
|
||||
webviewManager: MarkdownPreviewManager
|
||||
) {
|
||||
this.#cspArbiter = cspArbiter;
|
||||
this.#webviewManager = webviewManager;
|
||||
}
|
||||
|
||||
public async showSecuritySelectorForResource(resource: vscode.Uri): Promise<void> {
|
||||
interface PreviewSecurityPickItem extends vscode.QuickPickItem {
|
||||
@@ -99,7 +111,7 @@ export class PreviewSecuritySelector {
|
||||
return when ? '• ' : '';
|
||||
}
|
||||
|
||||
const currentSecurityLevel = this._cspArbiter.getSecurityLevelForResource(resource);
|
||||
const currentSecurityLevel = this.#cspArbiter.getSecurityLevelForResource(resource);
|
||||
const selection = await vscode.window.showQuickPick<PreviewSecurityPickItem>(
|
||||
[
|
||||
{
|
||||
@@ -124,7 +136,7 @@ export class PreviewSecuritySelector {
|
||||
description: ''
|
||||
}, {
|
||||
type: 'toggle',
|
||||
label: this._cspArbiter.shouldDisableSecurityWarnings()
|
||||
label: this.#cspArbiter.shouldDisableSecurityWarnings()
|
||||
? vscode.l10n.t("Enable preview security warnings in this workspace")
|
||||
: vscode.l10n.t("Disable preview security warning in this workspace"),
|
||||
description: vscode.l10n.t("Does not affect the content security level")
|
||||
@@ -142,12 +154,12 @@ export class PreviewSecuritySelector {
|
||||
}
|
||||
|
||||
if (selection.type === 'toggle') {
|
||||
this._cspArbiter.setShouldDisableSecurityWarning(!this._cspArbiter.shouldDisableSecurityWarnings());
|
||||
this._webviewManager.refresh();
|
||||
this.#cspArbiter.setShouldDisableSecurityWarning(!this.#cspArbiter.shouldDisableSecurityWarnings());
|
||||
this.#webviewManager.refresh();
|
||||
return;
|
||||
} else {
|
||||
await this._cspArbiter.setSecurityLevelForResource(resource, selection.type);
|
||||
await this.#cspArbiter.setSecurityLevelForResource(resource, selection.type);
|
||||
}
|
||||
this._webviewManager.refresh();
|
||||
this.#webviewManager.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ export interface LastScrollLocation {
|
||||
|
||||
export class TopmostLineMonitor extends Disposable {
|
||||
|
||||
private readonly _pendingUpdates = new ResourceMap<number>();
|
||||
private readonly _throttle = 50;
|
||||
private readonly _previousTextEditorInfo = new ResourceMap<LastScrollLocation>();
|
||||
private readonly _previousStaticEditorInfo = new ResourceMap<LastScrollLocation>();
|
||||
readonly #pendingUpdates = new ResourceMap<number>();
|
||||
readonly #throttle = 50;
|
||||
readonly #previousTextEditorInfo = new ResourceMap<LastScrollLocation>();
|
||||
readonly #previousStaticEditorInfo = new ResourceMap<LastScrollLocation>();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -39,32 +39,32 @@ export class TopmostLineMonitor extends Disposable {
|
||||
}));
|
||||
}
|
||||
|
||||
private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri; readonly line: number }>());
|
||||
public readonly onDidChanged = this._onChanged.event;
|
||||
readonly #onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri; readonly line: number }>());
|
||||
public readonly onDidChanged = this.#onChanged.event;
|
||||
|
||||
public setPreviousStaticEditorLine(scrollLocation: LastScrollLocation): void {
|
||||
this._previousStaticEditorInfo.set(scrollLocation.uri, scrollLocation);
|
||||
this.#previousStaticEditorInfo.set(scrollLocation.uri, scrollLocation);
|
||||
}
|
||||
|
||||
public getPreviousStaticEditorLineByUri(resource: vscode.Uri): number | undefined {
|
||||
const scrollLoc = this._previousStaticEditorInfo.get(resource);
|
||||
this._previousStaticEditorInfo.delete(resource);
|
||||
const scrollLoc = this.#previousStaticEditorInfo.get(resource);
|
||||
this.#previousStaticEditorInfo.delete(resource);
|
||||
return scrollLoc?.line;
|
||||
}
|
||||
|
||||
|
||||
public setPreviousTextEditorLine(scrollLocation: LastScrollLocation): void {
|
||||
this._previousTextEditorInfo.set(scrollLocation.uri, scrollLocation);
|
||||
this.#previousTextEditorInfo.set(scrollLocation.uri, scrollLocation);
|
||||
}
|
||||
|
||||
public getPreviousTextEditorLineByUri(resource: vscode.Uri): number | undefined {
|
||||
const scrollLoc = this._previousTextEditorInfo.get(resource);
|
||||
this._previousTextEditorInfo.delete(resource);
|
||||
const scrollLoc = this.#previousTextEditorInfo.get(resource);
|
||||
this.#previousTextEditorInfo.delete(resource);
|
||||
return scrollLoc?.line;
|
||||
}
|
||||
|
||||
public getPreviousStaticTextEditorLineByUri(resource: vscode.Uri): number | undefined {
|
||||
const state = this._previousStaticEditorInfo.get(resource);
|
||||
const state = this.#previousStaticEditorInfo.get(resource);
|
||||
return state?.line;
|
||||
}
|
||||
|
||||
@@ -72,20 +72,20 @@ export class TopmostLineMonitor extends Disposable {
|
||||
resource: vscode.Uri,
|
||||
line: number
|
||||
) {
|
||||
if (!this._pendingUpdates.has(resource)) {
|
||||
if (!this.#pendingUpdates.has(resource)) {
|
||||
// schedule update
|
||||
setTimeout(() => {
|
||||
if (this._pendingUpdates.has(resource)) {
|
||||
this._onChanged.fire({
|
||||
if (this.#pendingUpdates.has(resource)) {
|
||||
this.#onChanged.fire({
|
||||
resource,
|
||||
line: this._pendingUpdates.get(resource) as number
|
||||
line: this.#pendingUpdates.get(resource) as number
|
||||
});
|
||||
this._pendingUpdates.delete(resource);
|
||||
this.#pendingUpdates.delete(resource);
|
||||
}
|
||||
}, this._throttle);
|
||||
}, this.#throttle);
|
||||
}
|
||||
|
||||
this._pendingUpdates.set(resource, line);
|
||||
this.#pendingUpdates.set(resource, line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,21 +24,21 @@ const nullReporter = new class NullTelemetryReporter implements TelemetryReporte
|
||||
};
|
||||
|
||||
class ExtensionReporter implements TelemetryReporter {
|
||||
private readonly _reporter: VSCodeTelemetryReporter;
|
||||
readonly #reporter: VSCodeTelemetryReporter;
|
||||
|
||||
constructor(
|
||||
packageInfo: IPackageInfo
|
||||
) {
|
||||
this._reporter = new VSCodeTelemetryReporter(packageInfo.aiKey);
|
||||
this.#reporter = new VSCodeTelemetryReporter(packageInfo.aiKey);
|
||||
}
|
||||
sendTelemetryEvent(eventName: string, properties?: {
|
||||
[key: string]: string;
|
||||
}) {
|
||||
this._reporter.sendTelemetryEvent(eventName, properties);
|
||||
this.#reporter.sendTelemetryEvent(eventName, properties);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._reporter.dispose();
|
||||
this.#reporter.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ const emptyContributions = new class implements MarkdownContributionProvider {
|
||||
readonly extensionUri = vscode.Uri.file('/');
|
||||
readonly contributions = MarkdownContributions.Empty;
|
||||
|
||||
private readonly _onContributionsChanged = new vscode.EventEmitter<this>();
|
||||
readonly onContributionsChanged = this._onContributionsChanged.event;
|
||||
readonly #onContributionsChanged = new vscode.EventEmitter<this>();
|
||||
readonly onContributionsChanged = this.#onContributionsChanged.event;
|
||||
|
||||
dispose() {
|
||||
this._onContributionsChanged.dispose();
|
||||
this.#onContributionsChanged.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,55 +10,55 @@ export interface ITask<T> {
|
||||
export class Delayer<T> {
|
||||
|
||||
public defaultDelay: number;
|
||||
private _timeout: any; // Timer
|
||||
private _cancelTimeout: Promise<T | null> | null;
|
||||
private _onSuccess: ((value: T | PromiseLike<T> | undefined) => void) | null;
|
||||
private _task: ITask<T> | null;
|
||||
#timeout: any; // Timer
|
||||
#cancelTimeout: Promise<T | null> | null;
|
||||
#onSuccess: ((value: T | PromiseLike<T> | undefined) => void) | null;
|
||||
#task: ITask<T> | null;
|
||||
|
||||
constructor(defaultDelay: number) {
|
||||
this.defaultDelay = defaultDelay;
|
||||
this._timeout = null;
|
||||
this._cancelTimeout = null;
|
||||
this._onSuccess = null;
|
||||
this._task = null;
|
||||
this.#timeout = null;
|
||||
this.#cancelTimeout = null;
|
||||
this.#onSuccess = null;
|
||||
this.#task = null;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._doCancelTimeout();
|
||||
this.#doCancelTimeout();
|
||||
}
|
||||
|
||||
public trigger(task: ITask<T>, delay: number = this.defaultDelay): Promise<T | null> {
|
||||
this._task = task;
|
||||
this.#task = task;
|
||||
if (delay >= 0) {
|
||||
this._doCancelTimeout();
|
||||
this.#doCancelTimeout();
|
||||
}
|
||||
|
||||
if (!this._cancelTimeout) {
|
||||
this._cancelTimeout = new Promise<T | undefined>((resolve) => {
|
||||
this._onSuccess = resolve;
|
||||
if (!this.#cancelTimeout) {
|
||||
this.#cancelTimeout = new Promise<T | undefined>((resolve) => {
|
||||
this.#onSuccess = resolve;
|
||||
}).then(() => {
|
||||
this._cancelTimeout = null;
|
||||
this._onSuccess = null;
|
||||
const result = this._task?.() ?? null;
|
||||
this._task = null;
|
||||
this.#cancelTimeout = null;
|
||||
this.#onSuccess = null;
|
||||
const result = this.#task?.() ?? null;
|
||||
this.#task = null;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
if (delay >= 0 || this._timeout === null) {
|
||||
this._timeout = setTimeout(() => {
|
||||
this._timeout = null;
|
||||
this._onSuccess?.(undefined);
|
||||
if (delay >= 0 || this.#timeout === null) {
|
||||
this.#timeout = setTimeout(() => {
|
||||
this.#timeout = null;
|
||||
this.#onSuccess?.(undefined);
|
||||
}, delay >= 0 ? delay : this.defaultDelay);
|
||||
}
|
||||
|
||||
return this._cancelTimeout;
|
||||
return this.#cancelTimeout;
|
||||
}
|
||||
|
||||
private _doCancelTimeout(): void {
|
||||
if (this._timeout !== null) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
#doCancelTimeout(): void {
|
||||
if (this.#timeout !== null) {
|
||||
clearTimeout(this.#timeout);
|
||||
this.#timeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export const noopToken: vscode.CancellationToken = new class implements vscode.CancellationToken {
|
||||
private readonly _onCancellationRequestedEmitter = new vscode.EventEmitter<void>();
|
||||
onCancellationRequested = this._onCancellationRequestedEmitter.event;
|
||||
readonly #onCancellationRequestedEmitter = new vscode.EventEmitter<void>();
|
||||
onCancellationRequested = this.#onCancellationRequestedEmitter.event;
|
||||
|
||||
get isCancellationRequested() { return false; }
|
||||
};
|
||||
|
||||
@@ -28,20 +28,20 @@ export interface IDisposable {
|
||||
}
|
||||
|
||||
export abstract class Disposable {
|
||||
private _isDisposed = false;
|
||||
#isDisposed = false;
|
||||
|
||||
protected _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public dispose(): any {
|
||||
if (this._isDisposed) {
|
||||
if (this.#isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
this.#isDisposed = true;
|
||||
disposeAll(this._disposables);
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(value: T): T {
|
||||
if (this._isDisposed) {
|
||||
if (this.#isDisposed) {
|
||||
value.dispose();
|
||||
} else {
|
||||
this._disposables.push(value);
|
||||
@@ -50,6 +50,6 @@ export abstract class Disposable {
|
||||
}
|
||||
|
||||
protected get isDisposed() {
|
||||
return this._isDisposed;
|
||||
return this.#isDisposed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,20 @@ enum OpenMarkdownLinks {
|
||||
|
||||
export class MdLinkOpener {
|
||||
|
||||
readonly #client: MdLanguageClient;
|
||||
|
||||
constructor(
|
||||
private readonly _client: MdLanguageClient,
|
||||
) { }
|
||||
client: MdLanguageClient,
|
||||
) {
|
||||
this.#client = client;
|
||||
}
|
||||
|
||||
public async resolveDocumentLink(linkText: string, fromResource: vscode.Uri): Promise<proto.ResolvedDocumentLinkTarget> {
|
||||
return this._client.resolveLinkTarget(linkText, fromResource);
|
||||
return this.#client.resolveLinkTarget(linkText, fromResource);
|
||||
}
|
||||
|
||||
public async openDocumentLink(linkText: string, fromResource: vscode.Uri, viewColumn?: vscode.ViewColumn): Promise<void> {
|
||||
const resolved = await this._client.resolveLinkTarget(linkText, fromResource);
|
||||
const resolved = await this.#client.resolveLinkTarget(linkText, fromResource);
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,53 +11,53 @@ const defaultResourceToKey = (resource: vscode.Uri): string => resource.toString
|
||||
|
||||
export class ResourceMap<T> {
|
||||
|
||||
private readonly _map = new Map<string, { readonly uri: vscode.Uri; readonly value: T }>();
|
||||
readonly #map = new Map<string, { readonly uri: vscode.Uri; readonly value: T }>();
|
||||
|
||||
private readonly _toKey: ResourceToKey;
|
||||
readonly #toKey: ResourceToKey;
|
||||
|
||||
constructor(toKey: ResourceToKey = defaultResourceToKey) {
|
||||
this._toKey = toKey;
|
||||
this.#toKey = toKey;
|
||||
}
|
||||
|
||||
public set(uri: vscode.Uri, value: T): this {
|
||||
this._map.set(this._toKey(uri), { uri, value });
|
||||
this.#map.set(this.#toKey(uri), { uri, value });
|
||||
return this;
|
||||
}
|
||||
|
||||
public get(resource: vscode.Uri): T | undefined {
|
||||
return this._map.get(this._toKey(resource))?.value;
|
||||
return this.#map.get(this.#toKey(resource))?.value;
|
||||
}
|
||||
|
||||
public has(resource: vscode.Uri): boolean {
|
||||
return this._map.has(this._toKey(resource));
|
||||
return this.#map.has(this.#toKey(resource));
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this._map.size;
|
||||
return this.#map.size;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._map.clear();
|
||||
this.#map.clear();
|
||||
}
|
||||
|
||||
public delete(resource: vscode.Uri): boolean {
|
||||
return this._map.delete(this._toKey(resource));
|
||||
return this.#map.delete(this.#toKey(resource));
|
||||
}
|
||||
|
||||
public *values(): IterableIterator<T> {
|
||||
for (const entry of this._map.values()) {
|
||||
for (const entry of this.#map.values()) {
|
||||
yield entry.value;
|
||||
}
|
||||
}
|
||||
|
||||
public *keys(): IterableIterator<vscode.Uri> {
|
||||
for (const entry of this._map.values()) {
|
||||
for (const entry of this.#map.values()) {
|
||||
yield entry.uri;
|
||||
}
|
||||
}
|
||||
|
||||
public *entries(): IterableIterator<[vscode.Uri, T]> {
|
||||
for (const entry of this._map.values()) {
|
||||
for (const entry of this.#map.values()) {
|
||||
yield [entry.uri, entry.value];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user