Skip to content

Commit

Permalink
Watch changes on directories
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew committed Jan 9, 2025
1 parent 3f9de4c commit bbb5653
Show file tree
Hide file tree
Showing 21 changed files with 601 additions and 95 deletions.
22 changes: 11 additions & 11 deletions packages/langium-sprotty/test/trace-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('DefaultTraceProvider', async () => {
node a {
node b {}
}
`, { documentUri: 'test://test.model' });
`, { documentUri: 'test:/test.txt' });
const model = document.parseResult.value;
const source = model.nodes[0].nodes[0];
expect(source).toBeDefined();
Expand All @@ -47,20 +47,20 @@ describe('DefaultTraceProvider', async () => {
id: 'node0'
};
services.diagram.TraceProvider.trace(target, source);
expect(target.trace).toBe('test://test.model?2%3A16-2%3A25#%2Fnodes%400%2Fnodes%400');
expect(target.trace).toBe('test:/test.txt?2%3A16-2%3A25#%2Fnodes%400%2Fnodes%400');
});

test('finds source node', async () => {
const document = await parser(`
node a {
node b {}
}
`, { documentUri: 'test://test.model' });
`, { documentUri: 'test:/test.txt' });
const model = document.parseResult.value;
const target: TracedModelElement = {
type: 'node',
id: 'node0',
trace: 'test://test.model?2%3A16-2%3A25#%2Fnodes%400%2Fnodes%400'
trace: 'test:/test.txt?2%3A16-2%3A25#%2Fnodes%400%2Fnodes%400'
};
const source = services.diagram.TraceProvider.getSource(target);
expect(source).toBeDefined();
Expand All @@ -72,7 +72,7 @@ describe('DefaultTraceProvider', async () => {
node a {
node b {}
}
`, { documentUri: 'test://test.model' });
`, { documentUri: 'test:/test.txt' });
const model = document.parseResult.value;
const source = model.nodes[0].nodes[0];
expect(source).toBeDefined();
Expand All @@ -87,7 +87,7 @@ describe('DefaultTraceProvider', async () => {
<TracedModelElement>{
type: 'node',
id: 'node1',
trace: 'test://test.model?2%3A16-2%3A25#%2Fnodes%400%2Fnodes%400'
trace: 'test:/test.txt?2%3A16-2%3A25#%2Fnodes%400%2Fnodes%400'
}
]
};
Expand All @@ -101,7 +101,7 @@ describe('DefaultTraceProvider', async () => {
node a {
node b {}
}
`, { documentUri: 'test://test.model' });
`, { documentUri: 'test:/test.txt' });
const model = document.parseResult.value;
const source = model.nodes[0].nodes[0];
expect(source).toBeDefined();
Expand All @@ -116,7 +116,7 @@ describe('DefaultTraceProvider', async () => {
<TracedModelElement>{
type: 'node',
id: 'node0',
trace: 'test://test.model?1%3A12-3%3A13#%2Fnodes%400'
trace: 'test:/test.txt?1%3A12-3%3A13#%2Fnodes%400'
}
]
};
Expand All @@ -130,7 +130,7 @@ describe('DefaultTraceProvider', async () => {
node a {
node b {}
}
`, { documentUri: 'test://test.model' });
`, { documentUri: 'test:/test.txt' });
const model = document.parseResult.value;
const source = model.nodes[0].nodes[0];
expect(source).toBeDefined();
Expand All @@ -141,12 +141,12 @@ describe('DefaultTraceProvider', async () => {
<TracedModelElement>{
type: 'node',
id: 'node0',
trace: 'test://test.model?1%3A12-3%3A13#%2Fnodes%400'
trace: 'test:/test.txt?1%3A12-3%3A13#%2Fnodes%400'
},
<TracedModelElement>{
type: 'node',
id: 'node1',
trace: 'test://test.model?2%3A16-2%3A25#%2Fnodes%400%2Fnodes%400'
trace: 'test:/test.txt?2%3A16-2%3A25#%2Fnodes%400%2Fnodes%400'
}
]
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ export class LangiumGrammarWorkspaceManager extends DefaultWorkspaceManager {
return super.initializeWorkspace(folders, cancelToken);
}

protected override includeEntry(workspaceFolder: WorkspaceFolder, entry: FileSystemNode, fileExtensions: string[]): boolean {
if (this.matcher) {
override includeEntry(entry: FileSystemNode): boolean {
const workspaceFolder = this.workspaceFolders?.find(folder => UriUtils.contains(folder.uri, entry.uri));
if (this.matcher && workspaceFolder) {
// create path relative to workspace folder root: /user/foo/workspace/entry.txt -> entry.txt
const relPath = path.relative(URI.parse(workspaceFolder.uri).path, entry.uri.path);
const ignored = this.matcher.ignores(relPath);
return !ignored && (entry.isDirectory || (entry.isFile && fileExtensions.includes(UriUtils.extname(entry.uri))));
return !ignored && (entry.isDirectory || (entry.isFile && this.fileExtensions.has(UriUtils.extname(entry.uri))));
}
return super.includeEntry(workspaceFolder, entry, fileExtensions);
return super.includeEntry(entry);
}

}
2 changes: 1 addition & 1 deletion packages/langium/src/grammar/internal-grammar-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export async function createServicesForGrammar<L extends LangiumServices = Langi
};
const languageMetaData = config.languageMetaData ?? {
caseInsensitive: false,
fileExtensions: [`.${grammarNode.name?.toLowerCase() ?? 'unknown'}`],
fileExtensions: ['.txt'],
languageId: grammarNode.name ?? 'UNKNOWN',
mode: 'development'
};
Expand Down
28 changes: 9 additions & 19 deletions packages/langium/src/lsp/document-update-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,28 +92,18 @@ export class DefaultDocumentUpdateHandler implements DocumentUpdateHandler {
}

protected registerFileWatcher(services: LangiumSharedServices): void {
const fileExtensions = stream(services.ServiceRegistry.all)
.flatMap(language => language.LanguageMetaData.fileExtensions)
.map(ext => ext.startsWith('.') ? ext.substring(1) : ext)
.distinct()
.toArray();
if (fileExtensions.length > 0) {
const connection = services.lsp.Connection;
const options: DidChangeWatchedFilesRegistrationOptions = {
watchers: [{
globPattern: fileExtensions.length === 1
? `**/*.${fileExtensions[0]}`
: `**/*.{${fileExtensions.join(',')}}`
}]
};
connection?.client.register(DidChangeWatchedFilesNotification.type, options);
}
const connection = services.lsp.Connection;
const options: DidChangeWatchedFilesRegistrationOptions = {
watchers: [{
// We need to watch all file changes in the workspace
// Otherwise we miss changes to directories
globPattern: '**/*'
}]
};
connection?.client.register(DidChangeWatchedFilesNotification.type, options);
}

protected fireDocumentUpdate(changed: URI[], deleted: URI[]): void {
// Filter out URIs that do not have a service in the registry
// Running the document builder update will fail for those URIs
changed = changed.filter(uri => this.serviceRegistry.hasServices(uri));
// Only fire the document update when the workspace manager is ready
// Otherwise, we might miss the initial indexing of the workspace
this.workspaceManager.ready.then(() => {
Expand Down
18 changes: 18 additions & 0 deletions packages/langium/src/node/node-file-system-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ export class NodeFileSystemProvider implements FileSystemProvider {

encoding: NodeTextEncoding = 'utf-8';

async stat(uri: URI): Promise<FileSystemNode> {
const stat = await fs.promises.stat(uri.fsPath);
return {
isFile: stat.isFile(),
isDirectory: stat.isDirectory(),
uri
};
}

statSync(uri: URI): FileSystemNode {
const stat = fs.statSync(uri.fsPath);
return {
isFile: stat.isFile(),
isDirectory: stat.isDirectory(),
uri
};
}

readFile(uri: URI): Promise<string> {
return fs.promises.readFile(uri.fsPath, this.encoding);
}
Expand Down
9 changes: 0 additions & 9 deletions packages/langium/src/service-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export interface ServiceRegistry {
*/
export class DefaultServiceRegistry implements ServiceRegistry {

protected singleton?: LangiumCoreServices;
protected readonly languageIdMap = new Map<string, LangiumCoreServices>();
protected readonly fileExtensionMap = new Map<string, LangiumCoreServices>();

Expand All @@ -67,17 +66,9 @@ export class DefaultServiceRegistry implements ServiceRegistry {
this.fileExtensionMap.set(ext, language);
}
this.languageIdMap.set(data.languageId, language);
if (this.languageIdMap.size === 1) {
this.singleton = language;
} else {
this.singleton = undefined;
}
}

getServices(uri: URI): LangiumCoreServices {
if (this.singleton !== undefined) {
return this.singleton;
}
if (this.languageIdMap.size === 0) {
throw new Error('The service registry is empty. Use `register` to register the services of a language.');
}
Expand Down
1 change: 1 addition & 0 deletions packages/langium/src/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
*/

export * from './langium-test.js';
export * from './virtual-file-system.js';
62 changes: 62 additions & 0 deletions packages/langium/src/test/virtual-file-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/******************************************************************************
* Copyright 2024 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import { URI } from 'vscode-uri';
import type { FileSystemNode, FileSystemProvider } from '../workspace/file-system-provider.js';
import { UriTrie } from '../utils/uri-utils.js';

export class VirtualFileSystemProvider implements FileSystemProvider {

private readonly trie = new UriTrie<string>();

insert(uri: URI | string, content: string): void {
this.trie.insert(uri, content);
}

delete(uri: URI | string): void {
this.trie.delete(uri);
}

stat(uri: URI): Promise<FileSystemNode> {
return Promise.resolve(this.statSync(uri));
}

statSync(uri: URI): FileSystemNode {
const node = this.trie.findNode(uri);
if (node) {
return {
isDirectory: node.element === undefined,
isFile: node.element !== undefined,
uri
};
} else {
throw new Error('File not found');
}
}

readFile(uri: URI): Promise<string> {
const data = this.trie.find(uri);
if (typeof data === 'string') {
return Promise.resolve(data);
} else {
throw new Error('File not found');
}
}

readDirectory(uri: URI): Promise<FileSystemNode[]> {
const node = this.trie.findNode(uri);
if (!node) {
throw new Error('Directory not found');
}
const children = this.trie.findChildren(uri);
return Promise.resolve(children.map(child => ({
isDirectory: child.element === undefined,
isFile: child.element !== undefined,
uri: URI.parse(child.uri)
})));
}

}
Loading

0 comments on commit bbb5653

Please sign in to comment.