diff options
author | 2021-04-30 16:33:35 -0500 | |
---|---|---|
committer | 2021-04-30 16:33:35 -0500 | |
commit | 4df1347156cf2632ea2f3475d3a5f8f08d197cc3 (patch) | |
tree | 9d50de89dfe62827c32a8a4046120af4ab61dc0c /tools/vscode/packages/server/src/plugins/PluginHost.ts | |
parent | 1d498facc8f78a3ffbfecd05cc6ecd45e8a4a1ae (diff) | |
download | astro-4df1347156cf2632ea2f3475d3a5f8f08d197cc3.tar.gz astro-4df1347156cf2632ea2f3475d3a5f8f08d197cc3.tar.zst astro-4df1347156cf2632ea2f3475d3a5f8f08d197cc3.zip |
Migrate to `yarn` monorepo (#157)
* chore: use monorepo
* chore: scaffold astro-scripts
* chore: move tests inside packages/astro
* chore: refactor tests, add scripts
* chore: move parser to own module
* chore: move runtime to packages/astro
* fix: move parser to own package
* test: fix prettier-plugin-astro tests
* fix: tests
* chore: update package-lock
* chore: add changesets
* fix: cleanup examples
* fix: starter example
* chore: update changeset config
* chore: update changeset config
* chore: setup changeset release workflow
* chore: bump lockfiles
* chore: prism => astro-prism
* fix: tsc --emitDeclarationOnly
* chore: final cleanup, switch to yarn
* chore: add lerna
* chore: update workflows to yarn
* chore: update workflows
* chore: remove lint workflow
* chore: add astro-dev script
* chore: add symlinked README
Diffstat (limited to 'tools/vscode/packages/server/src/plugins/PluginHost.ts')
-rw-r--r-- | tools/vscode/packages/server/src/plugins/PluginHost.ts | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/tools/vscode/packages/server/src/plugins/PluginHost.ts b/tools/vscode/packages/server/src/plugins/PluginHost.ts new file mode 100644 index 000000000..72f098ca1 --- /dev/null +++ b/tools/vscode/packages/server/src/plugins/PluginHost.ts @@ -0,0 +1,166 @@ + +import { + CompletionContext, + CompletionItem, + CompletionList, + Position, + TextDocumentIdentifier, +} from 'vscode-languageserver'; +import type { DocumentManager } from '../core/documents'; +import type * as d from './interfaces'; +import { flatten } from '../utils'; +import { FoldingRange } from 'vscode-languageserver-types'; + +// eslint-disable-next-line no-shadow +enum ExecuteMode { + None, + FirstNonNull, + Collect +} + +export class PluginHost { + private plugins: d.Plugin[] = []; + + constructor(private documentsManager: DocumentManager) {} + + register(plugin: d.Plugin) { + this.plugins.push(plugin); + } + + async getCompletions( + textDocument: TextDocumentIdentifier, + position: Position, + completionContext?: CompletionContext + ): Promise<CompletionList> { + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + const completions = ( + await this.execute<CompletionList>( + 'getCompletions', + [document, position, completionContext], + ExecuteMode.Collect + ) + ).filter((completion) => completion != null); + + let flattenedCompletions = flatten(completions.map((completion) => completion.items)); + const isIncomplete = completions.reduce( + (incomplete, completion) => incomplete || completion.isIncomplete, + false as boolean + ); + + return CompletionList.create(flattenedCompletions, isIncomplete); + } + + async resolveCompletion( + textDocument: TextDocumentIdentifier, + completionItem: d.AppCompletionItem + ): Promise<CompletionItem> { + const document = this.getDocument(textDocument.uri); + + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + const result = await this.execute<CompletionItem>( + 'resolveCompletion', + [document, completionItem], + ExecuteMode.FirstNonNull + ); + + return result ?? completionItem; + } + + async doTagComplete( + textDocument: TextDocumentIdentifier, + position: Position + ): Promise<string | null> { + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + return this.execute<string | null>( + 'doTagComplete', + [document, position], + ExecuteMode.FirstNonNull + ); + } + + async getFoldingRanges( + textDocument: TextDocumentIdentifier + ): Promise<FoldingRange[]|null> { + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + const foldingRanges = flatten(await this.execute<FoldingRange[]>( + 'getFoldingRanges', + [document], + ExecuteMode.Collect + )).filter((completion) => completion != null) + + return foldingRanges; + } + + onWatchFileChanges(onWatchFileChangesParams: any[]): void { + for (const support of this.plugins) { + support.onWatchFileChanges?.(onWatchFileChangesParams); + } + } + + private getDocument(uri: string) { + return this.documentsManager.get(uri); + } + + private execute<T>( + name: keyof d.LSProvider, + args: any[], + mode: ExecuteMode.FirstNonNull + ): Promise<T | null>; + private execute<T>( + name: keyof d.LSProvider, + args: any[], + mode: ExecuteMode.Collect + ): Promise<T[]>; + private execute(name: keyof d.LSProvider, args: any[], mode: ExecuteMode.None): Promise<void>; + private async execute<T>( + name: keyof d.LSProvider, + args: any[], + mode: ExecuteMode + ): Promise<(T | null) | T[] | void> { + const plugins = this.plugins.filter((plugin) => typeof plugin[name] === 'function'); + + switch (mode) { + case ExecuteMode.FirstNonNull: + for (const plugin of plugins) { + const res = await this.tryExecutePlugin(plugin, name, args, null); + if (res != null) { + return res; + } + } + return null; + case ExecuteMode.Collect: + return Promise.all( + plugins.map((plugin) => this.tryExecutePlugin(plugin, name, args, [])) + ); + case ExecuteMode.None: + await Promise.all( + plugins.map((plugin) => this.tryExecutePlugin(plugin, name, args, null)) + ); + return; + } + } + + private async tryExecutePlugin(plugin: any, fnName: string, args: any[], failValue: any) { + try { + return await plugin[fnName](...args); + } catch (e) { + console.error(e); + return failValue; + } + } +} |