Skip to content

Commit

Permalink
Add support for Delta documents
Browse files Browse the repository at this point in the history
  • Loading branch information
ddaspit committed Dec 10, 2024
1 parent 920740b commit 0d1693b
Show file tree
Hide file tree
Showing 47 changed files with 1,430 additions and 439 deletions.
75 changes: 73 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/core/src/diagnostic/diagnostic-fix.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { TextEdit } from '../common/text-edit';
import { Diagnostic } from './diagnostic';

export interface DiagnosticFix {
export interface DiagnosticFix<T = TextEdit> {
title: string;
diagnostic: Diagnostic;
isPreferred?: boolean;
edits: TextEdit[];
edits: T[];
}
5 changes: 3 additions & 2 deletions packages/core/src/diagnostic/diagnostic-provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Observable } from 'rxjs';

import { TextEdit } from '../common/text-edit';
import { Diagnostic } from './diagnostic';
import { DiagnosticFix } from './diagnostic-fix';

Expand All @@ -9,10 +10,10 @@ export interface DiagnosticsChanged {
diagnostics: Diagnostic[];
}

export interface DiagnosticProvider {
export interface DiagnosticProvider<T = TextEdit> {
readonly id: string;
readonly diagnosticsChanged$: Observable<DiagnosticsChanged>;
init(): Promise<void>;
getDiagnostics(uri: string): Promise<Diagnostic[]>;
getDiagnosticFixes(uri: string, diagnostic: Diagnostic): Promise<DiagnosticFix[]>;
getDiagnosticFixes(uri: string, diagnostic: Diagnostic): Promise<DiagnosticFix<T>[]>;
}
35 changes: 35 additions & 0 deletions packages/core/src/document/document-accessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Observable } from 'rxjs';

import { Document } from './document';

export interface DocumentCreated<T extends Document> {
document: T;
}

export interface DocumentClosed {
uri: string;
}

export interface DocumentOpened<T extends Document> {
document: T;
}

export interface DocumentDeleted {
uri: string;
}

export interface DocumentChanged<T extends Document> {
document: T;
}

export interface DocumentAccessor<T extends Document = Document> {
readonly created$: Observable<DocumentCreated<T>>;
readonly closed$: Observable<DocumentClosed>;
readonly opened$: Observable<DocumentOpened<T>>;
readonly deleted$: Observable<DocumentDeleted>;
readonly changed$: Observable<DocumentChanged<T>>;

get(uri: string): Promise<T | undefined>;
all(): Promise<T[]>;
active(): Promise<T[]>;
}
13 changes: 4 additions & 9 deletions packages/core/src/document/document-factory.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { Range } from '../common/range';
import { Document } from './document';
import { TextDocumentChange } from './text-document-change';

export interface DocumentChange {
range?: Range;
text: string;
}

export interface DocumentFactory<T extends Document> {
create(uri: string, format: string, version: number, content: string): T;
update(document: T, changes: readonly DocumentChange[], version: number): T;
export interface DocumentFactory<TDoc extends Document = Document, TChange = TextDocumentChange> {
create(uri: string, format: string, version: number, content: string): TDoc;
update(document: TDoc, changes: readonly TChange[], version: number): TDoc;
}
87 changes: 37 additions & 50 deletions packages/core/src/document/document-manager.test.ts
Original file line number Diff line number Diff line change
@@ -1,118 +1,105 @@
import { firstValueFrom } from 'rxjs';
import { describe, expect, it } from 'vitest';
import { mock, MockProxy } from 'vitest-mock-extended';

import { Document } from './document';
import { DocumentFactory } from './document-factory';
import { DocumentManager } from './document-manager';
import { DocumentReader } from './document-reader';
import { TextDocument } from './text-document';
import { TextDocumentFactory } from './text-document-factory';

describe('DocumentManager', () => {
it('all', async () => {
const env = new TestEnvironment();

await expect(env.docManager.all()).resolves.toEqual([
{ uri: 'file1', format: 'plaintext', version: 1, content: 'This is file1.' },
{ uri: 'file2', format: 'plaintext', version: 1, content: 'This is file2.' },
]);
const docs = await env.docManager.all();
expect(docs).toHaveLength(2);
expect(docs[0].content).toEqual('This is file1.');
expect(docs[1].content).toEqual('This is file2.');
});

it('get', async () => {
const env = new TestEnvironment();

await expect(env.docManager.get('file2')).resolves.toEqual({
uri: 'file2',
format: 'plaintext',
version: 1,
content: 'This is file2.',
});
const doc = await env.docManager.get('file2');
expect(doc).not.toBeNull();
expect(doc?.content).toEqual('This is file2.');
});

it('fire created event', async () => {
const env = new TestEnvironment();

expect.assertions(1);
env.docManager.created$.subscribe((e) => {
expect(e.document).toEqual({ uri: 'file1', format: 'plaintext', version: 1, content: 'This is file1.' });
});
const createdPromise = firstValueFrom(env.docManager.created$);
await env.docManager.fireCreated('file1');
const createdEvent = await createdPromise;
expect(createdEvent.document.uri).toEqual('file1');
expect(createdEvent.document.content).toEqual('This is file1.');
});

it('fire opened event', async () => {
const env = new TestEnvironment();

expect.assertions(3);
env.docManager.opened$.subscribe((e) => {
expect(e.document).toEqual({ uri: 'file1', format: 'plaintext', version: 1, content: 'This is opened file1.' });
});
const openedPromise = firstValueFrom(env.docManager.opened$);
await expect(env.docManager.active()).resolves.toHaveLength(0);
await env.docManager.fireOpened('file1', 'plaintext', 1, 'This is opened file1.');
await expect(env.docManager.active()).resolves.toHaveLength(1);
const openedEvent = await openedPromise;
expect(openedEvent.document.uri).toEqual('file1');
expect(openedEvent.document.content).toEqual('This is opened file1.');
});

it('fire closed event', async () => {
const env = new TestEnvironment();
await env.docManager.fireOpened('file1', 'plaintext', 1, 'content');

expect.assertions(3);
env.docManager.closed$.subscribe((e) => {
expect(e.uri).toEqual('file1');
});
const closedPromise = firstValueFrom(env.docManager.closed$);
await expect(env.docManager.active()).resolves.toHaveLength(1);
await env.docManager.fireClosed('file1');
await expect(env.docManager.active()).resolves.toHaveLength(0);
const closedEvent = await closedPromise;
expect(closedEvent.uri).toEqual('file1');
});

it('fire deleted event', async () => {
const env = new TestEnvironment();

expect.assertions(2);
env.docManager.deleted$.subscribe((e) => {
expect(e.uri).toEqual('file1');
});
const deletedPromise = firstValueFrom(env.docManager.deleted$);
await env.docManager.fireDeleted('file1');
env.docReader.keys.mockReturnValue(['file2']);
await expect(env.docManager.all()).resolves.toHaveLength(1);
const deletedEvent = await deletedPromise;
expect(deletedEvent.uri).toEqual('file1');
});

it('fire changed event', async () => {
const env = new TestEnvironment();

expect.assertions(2);
const sub = env.docManager.changed$.subscribe((e) => {
expect(e.document).toEqual({ uri: 'file1', format: 'plaintext', version: 2, content: 'This is changed file1.' });
});
const changedPromise = firstValueFrom(env.docManager.changed$);
await env.docManager.fireChanged('file1', [{ text: 'This is changed file1.' }], 2);
sub.unsubscribe();
const changedEvent = await changedPromise;
expect(changedEvent.document.uri).toEqual('file1');
expect(changedEvent.document.version).toEqual(2);
expect(changedEvent.document.content).toEqual('This is changed file1.');

// reload document from reader
await env.docManager.fireChanged('file1');
await expect(env.docManager.get('file1')).resolves.toEqual({
uri: 'file1',
format: 'plaintext',
version: 1,
content: 'This is file1.',
});
const doc = await env.docManager.get('file1');
expect(doc).not.toBeNull();
expect(doc?.content).toEqual('This is file1.');
});
});

class TestEnvironment {
readonly docReader: MockProxy<DocumentReader>;
readonly docFactory: MockProxy<DocumentFactory<Document>>;
readonly docManager: DocumentManager<Document>;
readonly docFactory: TextDocumentFactory;
readonly docManager: DocumentManager<TextDocument>;

constructor() {
this.docReader = mock<DocumentReader>();
this.docReader.read.mockImplementation((uri) => {
return Promise.resolve({ format: 'plaintext', version: 1, content: `This is ${uri}.` });
});
this.docReader.keys.mockReturnValue(['file1', 'file2']);

this.docFactory = mock<DocumentFactory<Document>>();
this.docFactory.create.mockImplementation((uri, format, version, content) => {
return { uri, format, version, content };
});
this.docFactory.update.mockImplementation((document, changes, version) => {
return { uri: document.uri, format: 'plaintext', version, content: changes[0].text };
});

this.docFactory = new TextDocumentFactory();
this.docManager = new DocumentManager(this.docFactory, this.docReader);
}
}
Loading

0 comments on commit 0d1693b

Please sign in to comment.