From 94556e197065adb9546aa36dd87dfbff4a2fcf6d Mon Sep 17 00:00:00 2001 From: Damien Daspit Date: Thu, 31 Oct 2024 21:57:52 -0500 Subject: [PATCH] Add on type formatting provider --- packages/core/src/common/index.ts | 1 + packages/core/src/common/text-edit.ts | 6 +++ packages/core/src/formatting/index.ts | 1 + .../formatting/on-type-formatting-provider.ts | 16 ++++++++ packages/core/src/index.ts | 1 + packages/core/src/workspace/workspace.ts | 37 ++++++++++++++++++- packages/eslint-config/library.mjs | 1 + packages/vscode/src/server.ts | 11 ++++++ 8 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/common/text-edit.ts create mode 100644 packages/core/src/formatting/index.ts create mode 100644 packages/core/src/formatting/on-type-formatting-provider.ts diff --git a/packages/core/src/common/index.ts b/packages/core/src/common/index.ts index f1c63e3..40a8145 100644 --- a/packages/core/src/common/index.ts +++ b/packages/core/src/common/index.ts @@ -1,2 +1,3 @@ export type { Position } from './position'; export type { Range } from './range'; +export type { TextEdit } from './text-edit'; diff --git a/packages/core/src/common/text-edit.ts b/packages/core/src/common/text-edit.ts new file mode 100644 index 0000000..4415f76 --- /dev/null +++ b/packages/core/src/common/text-edit.ts @@ -0,0 +1,6 @@ +import { Range } from './range'; + +export interface TextEdit { + range: Range; + newText: string; +} diff --git a/packages/core/src/formatting/index.ts b/packages/core/src/formatting/index.ts new file mode 100644 index 0000000..d5b9370 --- /dev/null +++ b/packages/core/src/formatting/index.ts @@ -0,0 +1 @@ +export type { OnTypeFormattingProvider, OnTypeFormattingProviderFactory } from './on-type-formatting-provider'; diff --git a/packages/core/src/formatting/on-type-formatting-provider.ts b/packages/core/src/formatting/on-type-formatting-provider.ts new file mode 100644 index 0000000..aa537e8 --- /dev/null +++ b/packages/core/src/formatting/on-type-formatting-provider.ts @@ -0,0 +1,16 @@ +import { Position } from '../common/position'; +import { TextEdit } from '../common/text-edit'; +import { Document } from '../document/document'; +import { DocumentManager } from '../document/document-manager'; + +export type OnTypeFormattingProviderFactory = ( + DocumentManager: DocumentManager, +) => OnTypeFormattingProvider; + +export interface OnTypeFormattingProvider { + readonly id: string; + + readonly onTypeTriggerCharacters: ReadonlySet; + + getOnTypeEdits(uri: string, position: Position, ch: string): Promise; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 58b433b..93ddc26 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,5 @@ export * from './common'; export * from './diagnostic'; export * from './document'; +export * from './formatting'; export * from './workspace'; diff --git a/packages/core/src/workspace/workspace.ts b/packages/core/src/workspace/workspace.ts index 0836773..1da4ce0 100644 --- a/packages/core/src/workspace/workspace.ts +++ b/packages/core/src/workspace/workspace.ts @@ -1,5 +1,7 @@ import { map, merge, Observable, tap } from 'rxjs'; +import { Position } from '../common/position'; +import { TextEdit } from '../common/text-edit'; import { Diagnostic } from '../diagnostic/diagnostic'; import { DiagnosticFix } from '../diagnostic/diagnostic-fix'; import { DiagnosticProvider, DiagnosticProviderFactory, DiagnosticsChanged } from '../diagnostic/diagnostic-provider'; @@ -7,15 +9,18 @@ import { Document } from '../document/document'; import { DocumentFactory } from '../document/document-factory'; import { DocumentManager } from '../document/document-manager'; import { DocumentReader } from '../document/document-reader'; +import { OnTypeFormattingProvider, OnTypeFormattingProviderFactory } from '../formatting/on-type-formatting-provider'; export interface WorkspaceConfig { documentReader?: DocumentReader; documentFactory: DocumentFactory; - diagnosticProviders: DiagnosticProviderFactory[]; + diagnosticProviders?: DiagnosticProviderFactory[]; + onTypeFormattingProviders?: OnTypeFormattingProviderFactory[]; } export class Workspace { private readonly diagnosticProviders: Map; + private readonly onTypeFormattingProviders: Map; private readonly lastDiagnosticChangedEvents = new Map(); public readonly documentManager: DocumentManager; @@ -24,7 +29,7 @@ export class Workspace { constructor(config: WorkspaceConfig) { this.documentManager = new DocumentManager(config.documentReader, config.documentFactory); this.diagnosticProviders = new Map( - config.diagnosticProviders.map((factory) => { + config.diagnosticProviders?.map((factory) => { const provider = factory(this.documentManager); return [provider.id, provider]; }), @@ -38,6 +43,12 @@ export class Workspace { ), ), ).pipe(map((e) => this.getCombinedDiagnosticChangedEvent(e.uri, e.version))); + this.onTypeFormattingProviders = new Map( + config.onTypeFormattingProviders?.map((factory) => { + const provider = factory(this.documentManager); + return [provider.id, provider]; + }), + ); } async getDiagnostics(uri: string): Promise { @@ -56,6 +67,28 @@ export class Workspace { return await provider.getDiagnosticFixes(uri, diagnostic); } + getOnTypeTriggerCharacters(): string[] { + const characters = new Set(); + for (const provider of this.onTypeFormattingProviders.values()) { + for (const ch of provider.onTypeTriggerCharacters) { + characters.add(ch); + } + } + return Array.from(characters); + } + + async getOnTypeEdits(uri: string, position: Position, ch: string): Promise { + for (const provider of this.onTypeFormattingProviders.values()) { + if (provider.onTypeTriggerCharacters.has(ch)) { + const edits = await provider.getOnTypeEdits(uri, position, ch); + if (edits != null) { + return edits; + } + } + } + return undefined; + } + private updateCombinedDiagnosticChangedEvent(providerIndex: number, event: DiagnosticsChanged) { const docEvents = this.lastDiagnosticChangedEvents.get(event.uri) ?? []; docEvents[providerIndex] = event; diff --git a/packages/eslint-config/library.mjs b/packages/eslint-config/library.mjs index 9b2a7dd..3bd7af6 100644 --- a/packages/eslint-config/library.mjs +++ b/packages/eslint-config/library.mjs @@ -32,6 +32,7 @@ export default tseslint.config( ], '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', }, }, { diff --git a/packages/vscode/src/server.ts b/packages/vscode/src/server.ts index d85821f..d660f31 100644 --- a/packages/vscode/src/server.ts +++ b/packages/vscode/src/server.ts @@ -52,6 +52,13 @@ connection.onInitialize((params: InitializeParams) => { codeActionProvider: true, }, }; + const onTypeTriggerCharacters = workspace.getOnTypeTriggerCharacters(); + if (onTypeTriggerCharacters.length > 0) { + result.capabilities.documentOnTypeFormattingProvider = { + firstTriggerCharacter: onTypeTriggerCharacters[0], + moreTriggerCharacter: onTypeTriggerCharacters.slice(1), + }; + } if (hasWorkspaceFolderCapability) { result.capabilities.workspace = { workspaceFolders: { @@ -130,6 +137,10 @@ connection.onDidChangeTextDocument((params) => { ); }); +connection.onDocumentOnTypeFormatting(async (params) => { + return await workspace.getOnTypeEdits(params.textDocument.uri, params.position, params.ch); +}); + connection.onDidChangeWatchedFiles((_change) => { // Monitored files have change in VSCode connection.console.log('We received a file change event');