mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-21 17:19:01 +01:00
Rework how markdown server works with documents (#160948)
* Rework how markdown server works with documents This rewrites how the markdown server works with documents. The goal is to better handle switching between in-memory versions of a doc (from `TextDocument`) and versions of the same doc on disk. From the markdown service's POV, there is only one type of document As part of this, I've also adopted the newest markdown language service version * Bump package-lock versions
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Connection, Emitter, FileChangeType, NotebookDocuments, TextDocuments } from 'vscode-languageserver';
|
||||
import { Connection, Emitter, FileChangeType, NotebookDocuments, Position, Range, TextDocuments } from 'vscode-languageserver';
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import * as md from 'vscode-markdown-languageservice';
|
||||
import { ContainingDocumentContext, FileWatcherOptions, IFileSystemWatcher } from 'vscode-markdown-languageservice/out/workspace';
|
||||
@@ -17,6 +17,66 @@ import { Schemes } from './util/schemes';
|
||||
|
||||
declare const TextDecoder: any;
|
||||
|
||||
class VsCodeDocument implements md.ITextDocument {
|
||||
|
||||
private inMemoryDoc?: TextDocument;
|
||||
private onDiskDoc?: TextDocument;
|
||||
|
||||
readonly uri: string;
|
||||
|
||||
constructor(uri: string, init: { inMemoryDoc: TextDocument });
|
||||
constructor(uri: string, init: { onDiskDoc: TextDocument });
|
||||
constructor(uri: string, init: { inMemoryDoc?: TextDocument; onDiskDoc?: TextDocument }) {
|
||||
this.uri = uri;
|
||||
this.inMemoryDoc = init?.inMemoryDoc;
|
||||
this.onDiskDoc = init?.onDiskDoc;
|
||||
}
|
||||
|
||||
get version(): number {
|
||||
return this.inMemoryDoc?.version ?? this.onDiskDoc?.version ?? 0;
|
||||
}
|
||||
|
||||
get lineCount(): number {
|
||||
return this.inMemoryDoc?.lineCount ?? this.onDiskDoc?.lineCount ?? 0;
|
||||
}
|
||||
|
||||
getText(range?: Range): string {
|
||||
if (this.inMemoryDoc) {
|
||||
return this.inMemoryDoc.getText(range);
|
||||
}
|
||||
|
||||
if (this.onDiskDoc) {
|
||||
return this.onDiskDoc.getText(range);
|
||||
}
|
||||
|
||||
throw new Error('Document has been closed');
|
||||
}
|
||||
|
||||
positionAt(offset: number): Position {
|
||||
if (this.inMemoryDoc) {
|
||||
return this.inMemoryDoc.positionAt(offset);
|
||||
}
|
||||
|
||||
if (this.onDiskDoc) {
|
||||
return this.onDiskDoc.positionAt(offset);
|
||||
}
|
||||
|
||||
throw new Error('Document has been closed');
|
||||
}
|
||||
|
||||
isDetached(): boolean {
|
||||
return !this.onDiskDoc && !this.inMemoryDoc;
|
||||
}
|
||||
|
||||
setInMemoryDoc(doc: TextDocument | undefined) {
|
||||
this.inMemoryDoc = doc;
|
||||
}
|
||||
|
||||
setOnDiskDoc(doc: TextDocument | undefined) {
|
||||
this.onDiskDoc = doc;
|
||||
}
|
||||
}
|
||||
|
||||
export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
||||
|
||||
private readonly _onDidCreateMarkdownDocument = new Emitter<md.ITextDocument>();
|
||||
@@ -28,7 +88,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
||||
private readonly _onDidDeleteMarkdownDocument = new Emitter<URI>();
|
||||
public readonly onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocument.event;
|
||||
|
||||
private readonly _documentCache = new ResourceMap<md.ITextDocument>();
|
||||
private readonly _documentCache = new ResourceMap<VsCodeDocument>();
|
||||
|
||||
private readonly _utf8Decoder = new TextDecoder('utf-8');
|
||||
|
||||
@@ -49,31 +109,68 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
||||
private readonly logger: md.ILogger,
|
||||
) {
|
||||
documents.onDidOpen(e => {
|
||||
this._documentCache.delete(URI.parse(e.document.uri));
|
||||
if (this.isRelevantMarkdownDocument(e.document)) {
|
||||
this._onDidCreateMarkdownDocument.fire(e.document);
|
||||
}
|
||||
});
|
||||
|
||||
documents.onDidChangeContent(e => {
|
||||
if (this.isRelevantMarkdownDocument(e.document)) {
|
||||
this._onDidChangeMarkdownDocument.fire(e.document);
|
||||
}
|
||||
});
|
||||
|
||||
documents.onDidClose(async e => {
|
||||
const uri = URI.parse(e.document.uri);
|
||||
this._documentCache.delete(uri);
|
||||
|
||||
if (!this.isRelevantMarkdownDocument(e.document)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the document has closed, the file on disk may still exist.
|
||||
// In this case, we want to replace the existing entry with the one from disk
|
||||
const doc = await this.openMarkdownDocumentFromFs(uri);
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: TextDocument.onDidOpen', `${e.document.uri}`);
|
||||
|
||||
const uri = URI.parse(e.document.uri);
|
||||
const doc = this._documentCache.get(uri);
|
||||
|
||||
if (doc) {
|
||||
// File already existed on disk
|
||||
doc.setInMemoryDoc(e.document);
|
||||
|
||||
// The content visible to the language service may have changed since the in-memory doc
|
||||
// may differ from the one on-disk. To be safe we always fire a change event.
|
||||
this._onDidChangeMarkdownDocument.fire(doc);
|
||||
} else {
|
||||
// We're creating the file for the first time
|
||||
const doc = new VsCodeDocument(e.document.uri, { inMemoryDoc: e.document });
|
||||
this._documentCache.set(uri, doc);
|
||||
this._onDidCreateMarkdownDocument.fire(doc);
|
||||
}
|
||||
});
|
||||
|
||||
documents.onDidChangeContent(e => {
|
||||
if (!this.isRelevantMarkdownDocument(e.document)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: TextDocument.onDidChanceContent', `${e.document.uri}`);
|
||||
|
||||
const uri = URI.parse(e.document.uri);
|
||||
const entry = this._documentCache.get(uri);
|
||||
if (entry) {
|
||||
entry.setInMemoryDoc(e.document);
|
||||
this._onDidChangeMarkdownDocument.fire(entry);
|
||||
}
|
||||
});
|
||||
|
||||
documents.onDidClose(async e => {
|
||||
if (!this.isRelevantMarkdownDocument(e.document)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: TextDocument.onDidClose', `${e.document.uri}`);
|
||||
|
||||
const uri = URI.parse(e.document.uri);
|
||||
const doc = this._documentCache.get(uri);
|
||||
if (!doc) {
|
||||
this._onDidDeleteMarkdownDocument.fire(uri);
|
||||
// Document was never opened
|
||||
return;
|
||||
}
|
||||
|
||||
doc.setInMemoryDoc(undefined);
|
||||
if (doc.isDetached()) {
|
||||
// The document has been fully closed
|
||||
this.doDeleteDocument(uri);
|
||||
} else {
|
||||
// The document still exists on disk
|
||||
// To be safe, tell the service that the document has changed because the
|
||||
// in-memory doc contents may be different than the disk doc contents.
|
||||
this._onDidChangeMarkdownDocument.fire(doc);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -83,23 +180,35 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: onDidChangeWatchedFiles', `${change.type}: ${resource}`);
|
||||
switch (change.type) {
|
||||
case FileChangeType.Changed: {
|
||||
this._documentCache.delete(resource);
|
||||
const document = await this.openMarkdownDocument(resource);
|
||||
if (document) {
|
||||
this._onDidChangeMarkdownDocument.fire(document);
|
||||
const entry = this._documentCache.get(resource);
|
||||
if (entry) {
|
||||
// Refresh the on-disk state
|
||||
const document = await this.openMarkdownDocumentFromFs(resource);
|
||||
if (document) {
|
||||
this._onDidChangeMarkdownDocument.fire(document);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FileChangeType.Created: {
|
||||
const document = await this.openMarkdownDocument(resource);
|
||||
if (document) {
|
||||
this._onDidCreateMarkdownDocument.fire(document);
|
||||
const entry = this._documentCache.get(resource);
|
||||
if (entry) {
|
||||
// Create or update the on-disk state
|
||||
const document = await this.openMarkdownDocumentFromFs(resource);
|
||||
if (document) {
|
||||
this._onDidCreateMarkdownDocument.fire(document);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FileChangeType.Deleted: {
|
||||
this._documentCache.delete(resource);
|
||||
this._onDidDeleteMarkdownDocument.fire(resource);
|
||||
const entry = this._documentCache.get(resource);
|
||||
if (entry) {
|
||||
entry.setOnDiskDoc(undefined);
|
||||
if (entry.isDetached()) {
|
||||
this.doDeleteDocument(resource);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -182,8 +291,15 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
||||
|
||||
const matchingDocument = this.documents.get(resource.toString());
|
||||
if (matchingDocument) {
|
||||
this._documentCache.set(resource, matchingDocument);
|
||||
return matchingDocument;
|
||||
let entry = this._documentCache.get(resource);
|
||||
if (entry) {
|
||||
entry.setInMemoryDoc(matchingDocument);
|
||||
} else {
|
||||
entry = new VsCodeDocument(resource.toString(), { inMemoryDoc: matchingDocument });
|
||||
this._documentCache.set(resource, entry);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
return this.openMarkdownDocumentFromFs(resource);
|
||||
@@ -201,7 +317,9 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
||||
|
||||
// We assume that markdown is in UTF-8
|
||||
const text = this._utf8Decoder.decode(bytes);
|
||||
const doc = TextDocument.create(resource.toString(), 'markdown', 0, text);
|
||||
const doc = new VsCodeDocument(resource.toString(), {
|
||||
onDiskDoc: TextDocument.create(resource.toString(), 'markdown', 0, text)
|
||||
});
|
||||
this._documentCache.set(resource, doc);
|
||||
return doc;
|
||||
} catch (e) {
|
||||
@@ -270,4 +388,11 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
||||
private isRelevantMarkdownDocument(doc: TextDocument) {
|
||||
return isMarkdownFile(doc) && URI.parse(doc.uri).scheme !== 'vscode-bulkeditpreview';
|
||||
}
|
||||
|
||||
private doDeleteDocument(uri: URI) {
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: deleteDocument', `${uri}`);
|
||||
|
||||
this._documentCache.delete(uri);
|
||||
this._onDidDeleteMarkdownDocument.fire(uri);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user