diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSessionProvider.java similarity index 89% rename from server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java rename to server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSessionProvider.java index c456010c4bd..00562a74364 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSesssionProvider.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebServerSessionProvider.java @@ -23,12 +23,12 @@ import org.jkiss.dbeaver.model.auth.impl.AbstractSessionPersistent; import org.jkiss.dbeaver.model.lsp.DBLServerSessionProvider; -public class LSPWebServerSesssionProvider implements DBLServerSessionProvider { +public class LSPWebServerSessionProvider implements DBLServerSessionProvider { @NotNull private final BaseWebSession session; - public LSPWebServerSesssionProvider(@NotNull BaseWebSession session) { + public LSPWebServerSessionProvider(@NotNull BaseWebSession session) { this.session = session; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java index e7493567ff7..7492e210176 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/websockets/lsp/LSPWebSocketEndpoint.java @@ -44,13 +44,13 @@ public void onOpen(Session wsSession, EndpointConfig endpointConfig) { wsSession.setMaxTextMessageBufferSize(Integer.MAX_VALUE); wsSession.setMaxBinaryMessageBufferSize(Integer.MAX_VALUE); - LSPWebServerSesssionProvider sessionProvider = new LSPWebServerSesssionProvider(webSession); + LSPWebServerSessionProvider sessionProvider = new LSPWebServerSessionProvider(webSession); DBLServer server = new DBLServer(sessionProvider); - var builder = new WebSocketLauncherBuilder(); - builder.setSession(wsSession); - builder.setLocalService(server); - builder.setRemoteInterface(LanguageClient.class); - Launcher launcher = builder.create(); + Launcher launcher = new WebSocketLauncherBuilder() + .setSession(wsSession) + .setLocalService(server) + .setRemoteInterface(LanguageClient.class) + .create(); server.connect(launcher.getRemoteProxy()); } diff --git a/webapp/packages/core-connections/src/ConnectionDialectResource.ts b/webapp/packages/core-connections/src/ConnectionDialectResource.ts index f58e5aa407a..d07071fbf08 100644 --- a/webapp/packages/core-connections/src/ConnectionDialectResource.ts +++ b/webapp/packages/core-connections/src/ConnectionDialectResource.ts @@ -12,7 +12,6 @@ import { CachedMapAllKey, CachedMapResource, type ResourceKey, resourceKeyList, import { GraphQLService, type SqlDialectInfo } from '@cloudbeaver/core-sdk'; import type { IConnectionInfoParams } from './CONNECTION_INFO_PARAM_SCHEMA.js'; -import type { IConnectionExecutionContextInfo } from './ConnectionExecutionContext/ConnectionExecutionContextResource.js'; import { ConnectionInfoActiveProjectKey, ConnectionInfoProjectKey, @@ -42,17 +41,6 @@ export class ConnectionDialectResource extends CachedMapResource !connectionInfoResource.isConnected(key))); } - async formatScript(context: IConnectionExecutionContextInfo, query: string): Promise { - const result = await this.graphQLService.sdk.formatSqlQuery({ - projectId: context.projectId, - connectionId: context.connectionId, - contextId: context.id, - query, - }); - - return result.query; - } - protected async loader( originalKey: ResourceKey, includes: string[], diff --git a/webapp/packages/core-sdk/src/queries/sql-editor/formatSqlQuery.gql b/webapp/packages/core-sdk/src/queries/sql-editor/formatSqlQuery.gql deleted file mode 100644 index ae87badc4fc..00000000000 --- a/webapp/packages/core-sdk/src/queries/sql-editor/formatSqlQuery.gql +++ /dev/null @@ -1,13 +0,0 @@ -query formatSqlQuery( - $projectId: ID! - $connectionId: ID! - $contextId: ID! - $query: String! -) { - query: sqlFormatQuery( - projectId: $projectId - connectionId: $connectionId - contextId: $contextId - query: $query - ) -} diff --git a/webapp/packages/plugin-codemirror6/package.json b/webapp/packages/plugin-codemirror6/package.json index 3c429143eb0..37dcf95297f 100644 --- a/webapp/packages/plugin-codemirror6/package.json +++ b/webapp/packages/plugin-codemirror6/package.json @@ -35,6 +35,7 @@ "@codemirror/lang-sql": "^6", "@codemirror/lang-xml": "^6", "@codemirror/language": "^6", + "@codemirror/lsp-client": "^6", "@codemirror/merge": "^6", "@codemirror/search": "^6", "@codemirror/state": "^6", diff --git a/webapp/packages/plugin-codemirror6/src/index.ts b/webapp/packages/plugin-codemirror6/src/index.ts index 7a124789bc7..7bc3b5a5a7e 100644 --- a/webapp/packages/plugin-codemirror6/src/index.ts +++ b/webapp/packages/plugin-codemirror6/src/index.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ export * from './Hyperlink/useHyperlink.js'; export * from '@codemirror/view'; export * from '@codemirror/state'; export * from '@codemirror/autocomplete'; +export * from '@codemirror/lsp-client'; export * from './highlightNewLine.js'; export { html as HTML_EDITOR } from '@codemirror/lang-html'; diff --git a/webapp/packages/plugin-set-common/package.json b/webapp/packages/plugin-set-common/package.json index df2262d75e0..2f27c1ea5ca 100644 --- a/webapp/packages/plugin-set-common/package.json +++ b/webapp/packages/plugin-set-common/package.json @@ -110,6 +110,7 @@ "@cloudbeaver/plugin-settings-panel": "workspace:*", "@cloudbeaver/plugin-sql-async-task-confirmation": "workspace:^", "@cloudbeaver/plugin-sql-editor": "workspace:*", + "@cloudbeaver/plugin-sql-editor-codemirror": "workspace:*", "@cloudbeaver/plugin-sql-editor-navigation-tab": "workspace:*", "@cloudbeaver/plugin-sql-editor-navigation-tab-script": "workspace:*", "@cloudbeaver/plugin-sql-editor-new": "workspace:*", diff --git a/webapp/packages/plugin-set-common/src/index.ts b/webapp/packages/plugin-set-common/src/index.ts index b493f11bb3a..881c73a5c76 100644 --- a/webapp/packages/plugin-set-common/src/index.ts +++ b/webapp/packages/plugin-set-common/src/index.ts @@ -90,6 +90,7 @@ import pluginSettingsPanel from '@cloudbeaver/plugin-settings-panel/module'; import pluginSqlEditor from '@cloudbeaver/plugin-sql-editor/module'; import pluginSqlEditorNavigationTab from '@cloudbeaver/plugin-sql-editor-navigation-tab/module'; import pluginSqlEditorNavigationTabScript from '@cloudbeaver/plugin-sql-editor-navigation-tab-script/module'; +import pluginSqlEditorCodemirror from '@cloudbeaver/plugin-sql-editor-codemirror/module'; import pluginSqlEditorNew from '@cloudbeaver/plugin-sql-editor-new/module'; import pluginSqlEditorScreen from '@cloudbeaver/plugin-sql-editor-screen/module'; import pluginSqlGenerator from '@cloudbeaver/plugin-sql-generator/module'; @@ -175,6 +176,7 @@ export const commonSet = [ pluginSqlEditorNavigationTab, pluginSqlEditorScreen, pluginSqlEditorNew, + pluginSqlEditorCodemirror, pluginSqlGenerator, pluginUserProfile, pluginUserProfileAdministration, diff --git a/webapp/packages/plugin-set-common/tsconfig.json b/webapp/packages/plugin-set-common/tsconfig.json index 2c341834f0f..eaa3ae4b816 100644 --- a/webapp/packages/plugin-set-common/tsconfig.json +++ b/webapp/packages/plugin-set-common/tsconfig.json @@ -274,6 +274,9 @@ { "path": "../plugin-sql-editor" }, + { + "path": "../plugin-sql-editor-codemirror" + }, { "path": "../plugin-sql-editor-navigation-tab" }, diff --git a/webapp/packages/plugin-sql-editor-codemirror/package.json b/webapp/packages/plugin-sql-editor-codemirror/package.json index b4e6cfdbd31..f299d9329e3 100644 --- a/webapp/packages/plugin-sql-editor-codemirror/package.json +++ b/webapp/packages/plugin-sql-editor-codemirror/package.json @@ -23,7 +23,9 @@ }, "dependencies": { "@cloudbeaver/core-blocks": "workspace:*", + "@cloudbeaver/core-di": "workspace:*", "@cloudbeaver/core-sdk": "workspace:*", + "@cloudbeaver/core-utils": "workspace:*", "@cloudbeaver/plugin-codemirror6": "workspace:*", "mobx": "^6", "mobx-react-lite": "^4", diff --git a/webapp/packages/plugin-sql-editor-codemirror/src/LSPConnectionService.ts b/webapp/packages/plugin-sql-editor-codemirror/src/LSPConnectionService.ts new file mode 100644 index 00000000000..4130b04237a --- /dev/null +++ b/webapp/packages/plugin-sql-editor-codemirror/src/LSPConnectionService.ts @@ -0,0 +1,183 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { injectable } from '@cloudbeaver/core-di'; +import { GlobalConstants } from '@cloudbeaver/core-utils'; +import { type Transport, LSPClient, languageServerExtensions } from '@cloudbeaver/plugin-codemirror6'; + +const LSP_ENDPOINT = 'ws/lsp'; +const RECONNECT_BASE_DELAY = 1000; +const MAX_RECONNECT_ATTEMPTS = 5; + +interface IReconnectingTransport extends Transport { + dispose(): void; +} + +interface ILSPPosition { + line: number; + character: number; +} + +interface ILSPTextEdit { + range: { start: ILSPPosition; end: ILSPPosition }; + newText: string; +} + +interface IDocumentFormattingParams { + textDocument: { uri: string }; + options: { tabSize: number; insertSpaces: boolean }; +} + +function createReconnectingTransport(uri: string): { ready: Promise; dispose(): void } { + let handlers: ((value: string) => void)[] = []; + let sock: WebSocket | null = null; + let reconnectAttempts = 0; + let reconnectTimeout: ReturnType | null = null; + let disposed = false; + let connected = false; + + const { promise: ready, resolve: resolveReady, reject: rejectReady } = Promise.withResolvers(); + + const transport: IReconnectingTransport = { + send(message: string) { + if (sock?.readyState === WebSocket.OPEN) { + sock.send(message); + } + }, + subscribe(handler) { + handlers.push(handler); + }, + unsubscribe(handler) { + handlers = handlers.filter(h => h !== handler); + }, + dispose() { + disposed = true; + + if (reconnectTimeout !== null) { + clearTimeout(reconnectTimeout); + } + + if (sock) { + sock.onopen = null; + sock.onmessage = null; + sock.onclose = null; + sock.onerror = null; + sock.close(); + sock = null; + } + + handlers = []; + }, + }; + + function connect() { + if (disposed) { + return; + } + + sock = new WebSocket(uri); + + sock.onopen = () => { + reconnectAttempts = 0; + + if (!connected) { + connected = true; + resolveReady(transport); + } + }; + + sock.onmessage = e => { + handlers.forEach(h => h(e.data as string)); + }; + + sock.onclose = () => { + if (disposed) { + return; + } + + if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { + reconnectAttempts++; + const delay = RECONNECT_BASE_DELAY * 2 ** (reconnectAttempts - 1); + console.warn(`[LSP] WebSocket closed, reconnecting in ${delay}ms (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`); + reconnectTimeout = setTimeout(connect, delay); + } else { + console.error('[LSP] Max reconnect attempts reached'); + rejectReady(new Error('LSP WebSocket connection failed after max reconnect attempts')); + } + }; + + sock.onerror = e => { + console.error('[LSP] WebSocket error:', e); + }; + } + + connect(); + + return { ready, dispose: transport.dispose }; +} + +@injectable() +export class LSPConnectionService { + private client: LSPClient | null = null; + private disposeTransport: (() => void) | null = null; + private refCount = 0; + + acquire(): LSPClient { + if (!this.client) { + this.client = new LSPClient({ + extensions: languageServerExtensions(), + }); + + const url = GlobalConstants.absoluteServiceWSUrl(LSP_ENDPOINT); + const { ready, dispose } = createReconnectingTransport(url); + this.disposeTransport = dispose; + + ready + .then(transport => { + if (this.client) { + this.client.connect(transport); + } + }) + .catch(error => { + console.error(error); + }); + } + + this.refCount++; + return this.client; + } + + async formatDocument(documentUri: string): Promise { + if (!this.client) { + return null; + } + + this.client.sync(); + + const result = await this.client.request('textDocument/formatting', { + textDocument: { uri: documentUri }, + options: { tabSize: 4, insertSpaces: true }, + }); + + if (!result?.length) { + return null; + } + + return result[0]!.newText; + } + + release(): void { + this.refCount--; + + if (this.refCount <= 0) { + this.client = null; + this.refCount = 0; + this.disposeTransport?.(); + this.disposeTransport = null; + } + } +} diff --git a/webapp/packages/plugin-sql-editor-codemirror/src/index.ts b/webapp/packages/plugin-sql-editor-codemirror/src/index.ts index 51bdac8811c..924c20d4994 100644 --- a/webapp/packages/plugin-sql-editor-codemirror/src/index.ts +++ b/webapp/packages/plugin-sql-editor-codemirror/src/index.ts @@ -9,5 +9,7 @@ export * from './SQLCodeEditor/SQLCodeEditorLoader.js'; export * from './SQLCodeEditor/useSQLCodeEditor.js'; export * from './useSqlDialectExtension.js'; +export * from './useLSPExtension.js'; +export * from './LSPConnectionService.js'; export * from './ACTIVE_QUERY_EXTENSION.js'; export * from './QUERY_STATUS_GUTTER_EXTENSION.js'; diff --git a/webapp/packages/plugin-sql-editor-codemirror/src/module.ts b/webapp/packages/plugin-sql-editor-codemirror/src/module.ts new file mode 100644 index 00000000000..dcaab497912 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-codemirror/src/module.ts @@ -0,0 +1,18 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { ModuleRegistry } from '@cloudbeaver/core-di'; + +import { LSPConnectionService } from './LSPConnectionService.js'; + +export default ModuleRegistry.add({ + name: '@cloudbeaver/plugin-sql-editor-codemirror', + + configure: serviceCollection => { + serviceCollection.addSingleton(LSPConnectionService); + }, +}); diff --git a/webapp/packages/plugin-sql-editor-codemirror/src/useLSPExtension.ts b/webapp/packages/plugin-sql-editor-codemirror/src/useLSPExtension.ts new file mode 100644 index 00000000000..607b4d3f23d --- /dev/null +++ b/webapp/packages/plugin-sql-editor-codemirror/src/useLSPExtension.ts @@ -0,0 +1,45 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { useEffect, useMemo } from 'react'; + +import { createLazyLoader, useLazyImport } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { type Compartment, type Extension } from '@cloudbeaver/plugin-codemirror6'; + +import { LSPConnectionService } from './LSPConnectionService.js'; + +const codemirrorPluginLoader = createLazyLoader(() => import('@cloudbeaver/plugin-codemirror6')); + +export interface ILSPExtensionOptions { + documentUri: string | null | undefined; +} + +export function useLSPExtension(options: ILSPExtensionOptions): [Compartment, Extension] | null { + const { documentUri } = options; + const codemirror = useLazyImport(codemirrorPluginLoader); + const lspConnectionService = useService(LSPConnectionService); + + const LSP_COMPARTMENT = useMemo(() => { + if (!codemirror) { + return null; + } + return new codemirror.Compartment(); + }, [codemirror]); + + const client = useMemo(() => lspConnectionService.acquire(), [lspConnectionService]); + + useEffect(() => () => lspConnectionService.release(), [lspConnectionService]); + + return useMemo(() => { + if (!LSP_COMPARTMENT || !client || !codemirror || !documentUri) { + return null; + } + + return [LSP_COMPARTMENT, client.plugin(documentUri)]; + }, [LSP_COMPARTMENT, client, codemirror, documentUri]); +} diff --git a/webapp/packages/plugin-sql-editor-codemirror/tsconfig.json b/webapp/packages/plugin-sql-editor-codemirror/tsconfig.json index 3072dc9d943..b531e55bdc5 100644 --- a/webapp/packages/plugin-sql-editor-codemirror/tsconfig.json +++ b/webapp/packages/plugin-sql-editor-codemirror/tsconfig.json @@ -13,9 +13,15 @@ { "path": "../core-cli" }, + { + "path": "../core-di" + }, { "path": "../core-sdk" }, + { + "path": "../core-utils" + }, { "path": "../plugin-codemirror6" } diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts index b9974a8dd75..3279f2ce45a 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -73,6 +73,14 @@ export class ResourceSqlDataSource extends BaseSqlDataSource { return key.projectId; } + override get lspDocumentUri(): string | null { + if (!this.resourceKey || !this.projectId) { + return null; + } + const { path } = getRmResourceKey(this.resourceKey); + return `lsp://${this.projectId}/${path}`; + } + get executionContext(): IConnectionExecutionContextInfo | undefined { const executionContext = this.state?.executionContext; if (!executionContext || !this.connectionInfoResource.has(createConnectionParam(executionContext.projectId, executionContext.connectionId))) { diff --git a/webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/SQLCodeEditorPanel.tsx b/webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/SQLCodeEditorPanel.tsx index 0e70e575abe..be03fbad1d8 100644 --- a/webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/SQLCodeEditorPanel.tsx +++ b/webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/SQLCodeEditorPanel.tsx @@ -22,6 +22,7 @@ import { SQLCodeEditor, useSQLCodeEditor, useSqlDialectExtension, + useLSPExtension, } from '@cloudbeaver/plugin-sql-editor-codemirror'; import { useHighlightExtensions } from '../useHighlightExtensions.js'; import { useSqlDialectAutocompletion } from '../useSqlDialectAutocompletion.js'; @@ -46,6 +47,12 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent const sqlDialect = useSqlDialectExtension(data.dialect); const highlightExtensions = useHighlightExtensions(sqlEditorSettingsService.highlightWhitespace); + const lspDocumentUri = + data.model.dataSource?.lspDocumentUri ?? + (data.model.dataSource?.projectId ? `lsp://${data.model.dataSource.projectId}/${data.model.state?.editorId}` : null); + + const lspExtension = useLSPExtension({ documentUri: lspDocumentUri }); + if (autocompletion) { extensions.set(...autocompletion); } @@ -60,6 +67,10 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent }); } + if (lspExtension) { + extensions.set(...lspExtension); + } + const dndBox = useDNDBox({ canDrop: context => context.has(DATA_CONTEXT_NAV_NODE), onDrop: async (context, mouse) => { diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts index 2374de03178..b5c439dbc64 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts @@ -77,6 +77,10 @@ export abstract class BaseSqlDataSource [ConnectionDialectResource, NotificationService]) -export class SqlDialectInfoService { - constructor( - private readonly connectionDialectResource: ConnectionDialectResource, - private readonly notificationService: NotificationService, - ) {} - - async formatScript(context: IConnectionExecutionContextInfo, query: string): Promise { - try { - return await this.connectionDialectResource.formatScript(context, query); - } catch (error: any) { - this.notificationService.logException(error, 'Failed to format script'); - } - return query; - } -} diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts b/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts index 3b6a886c2ad..c0b5a01b5a7 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts @@ -19,7 +19,7 @@ import { createLastPromiseGetter, type LastPromiseGetter } from '@cloudbeaver/co import type { ISqlEditorTabState } from '../ISqlEditorTabState.js'; import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures.js'; import type { ISqlEditorCursor } from '../SqlDataSource/ISqlDataSource.js'; -import { SqlDialectInfoService } from '../SqlDialectInfoService.js'; +import { LSPConnectionService } from '@cloudbeaver/plugin-sql-editor-codemirror'; import { SqlEditorService } from '../SqlEditorService.js'; import { type ISQLScriptSegment } from '../SQLParser.js'; import { SqlExecutionPlanService } from '../SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.js'; @@ -31,7 +31,7 @@ import { SqlEditorSettingsService } from '../SqlEditorSettingsService.js'; import { SqlEditorModelService } from '../SqlEditorModel/SqlEditorModelService.js'; interface ISQLEditorDataPrivate extends ISQLEditorData { - readonly sqlDialectInfoService: SqlDialectInfoService; + readonly lspConnectionService: LSPConnectionService; readonly connectionExecutionContextService: ConnectionExecutionContextService; readonly sqlQueryService: SqlQueryService; readonly sqlEditorService: SqlEditorService; @@ -56,7 +56,7 @@ const MAX_HINTS_LIMIT = 200; export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { const connectionExecutionContextService = useService(ConnectionExecutionContextService); const sqlQueryService = useService(SqlQueryService); - const sqlDialectInfoService = useService(SqlDialectInfoService); + const lspConnectionService = useService(LSPConnectionService); const sqlEditorService = useService(SqlEditorService); const notificationService = useService(NotificationService); const sqlExecutionPlanService = useService(SqlExecutionPlanService); @@ -155,26 +155,30 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { }, async formatScript(): Promise { - if (this.isDisabled || this.isScriptEmpty || !this.model.dataSource?.executionContext) { + if (this.isDisabled || this.isScriptEmpty) { return; } - const script = await this.model.getResolvedSegment(); + const documentUri = + this.model.dataSource?.lspDocumentUri ?? + (this.model.dataSource?.projectId ? `lsp://${this.model.dataSource.projectId}/${this.state.editorId}` : null); - if (!script) { + if (!documentUri) { return; } this.onExecute.execute(true); try { this.readonlyState = true; - const formatted = await this.sqlDialectInfoService.formatScript(this.model.dataSource.executionContext, script.query); + const formatted = await this.lspConnectionService.formatDocument(documentUri); - const cursorAnchor = this.model.cursor.anchor; - this.setScript(this.value.substring(0, script.begin) + formatted + this.value.substring(script.end), 'format', { - anchor: cursorAnchor, - head: cursorAnchor, - }); + if (formatted !== null) { + const cursorAnchor = this.model.cursor.anchor; + this.setScript(formatted, 'format', { + anchor: cursorAnchor, + head: cursorAnchor, + }); + } } finally { this.readonlyState = false; } @@ -352,9 +356,9 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { state, model, dialect: connectionDialectLoader.tryGetData, + lspConnectionService, connectionExecutionContextService, sqlQueryService, - sqlDialectInfoService, sqlEditorService, sqlExecutionPlanService, sqlResultTabsService, diff --git a/webapp/packages/plugin-sql-editor/src/index.ts b/webapp/packages/plugin-sql-editor/src/index.ts index f6a6a39475b..1c2cf91520a 100644 --- a/webapp/packages/plugin-sql-editor/src/index.ts +++ b/webapp/packages/plugin-sql-editor/src/index.ts @@ -46,7 +46,6 @@ export * from './SqlEditorModel/SqlEditorModelService.js'; export * from './DATA_CONTEXT_SQL_EDITOR_STATE.js'; export * from './getSqlEditorName.js'; export * from './QueryDataSource.js'; -export * from './SqlDialectInfoService.js'; export * from './ISqlEditorTabState.js'; export * from './SQLEditorLoader.js'; export * from './SqlEditorModeService.js'; diff --git a/webapp/packages/plugin-sql-editor/src/module.ts b/webapp/packages/plugin-sql-editor/src/module.ts index 7b4d16819d2..5e19e25a6fa 100644 --- a/webapp/packages/plugin-sql-editor/src/module.ts +++ b/webapp/packages/plugin-sql-editor/src/module.ts @@ -20,7 +20,6 @@ import { SqlEditorSettingsService } from './SqlEditorSettingsService.js'; import { SqlEditorService } from './SqlEditorService.js'; import { SqlEditorModeService } from './SqlEditorModeService.js'; import { SqlEditorGroupTabsBootstrap } from './SqlEditorGroupTabsBootstrap.js'; -import { SqlDialectInfoService } from './SqlDialectInfoService.js'; import { SqlDataSourceService } from './SqlDataSource/SqlDataSourceService.js'; import { LocalStorageSqlDataSourceBootstrap } from './SqlDataSource/LocalStorage/LocalStorageSqlDataSourceBootstrap.js'; import { MenuBootstrap } from './MenuBootstrap.js'; @@ -54,7 +53,6 @@ export default ModuleRegistry.add({ .addSingleton(SqlEditorService) .addSingleton(SqlEditorModeService) .addSingleton(SqlEditorGroupTabsBootstrap) - .addSingleton(SqlDialectInfoService) .addSingleton(SqlDataSourceService) .addSingleton(LocalStorageSqlDataSourceBootstrap) .addSingleton(SqlEditorModelService) diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 1cedd0bb076..d6a694eb37c 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -2595,6 +2595,7 @@ __metadata: "@codemirror/lang-sql": "npm:^6" "@codemirror/lang-xml": "npm:^6" "@codemirror/language": "npm:^6" + "@codemirror/lsp-client": "npm:^6" "@codemirror/merge": "npm:^6" "@codemirror/search": "npm:^6" "@codemirror/state": "npm:^6" @@ -3953,6 +3954,7 @@ __metadata: "@cloudbeaver/plugin-settings-panel": "workspace:*" "@cloudbeaver/plugin-sql-async-task-confirmation": "workspace:^" "@cloudbeaver/plugin-sql-editor": "workspace:*" + "@cloudbeaver/plugin-sql-editor-codemirror": "workspace:*" "@cloudbeaver/plugin-sql-editor-navigation-tab": "workspace:*" "@cloudbeaver/plugin-sql-editor-navigation-tab-script": "workspace:*" "@cloudbeaver/plugin-sql-editor-new": "workspace:*" @@ -4115,7 +4117,9 @@ __metadata: dependencies: "@cloudbeaver/core-blocks": "workspace:*" "@cloudbeaver/core-cli": "workspace:*" + "@cloudbeaver/core-di": "workspace:*" "@cloudbeaver/core-sdk": "workspace:*" + "@cloudbeaver/core-utils": "workspace:*" "@cloudbeaver/plugin-codemirror6": "workspace:*" "@cloudbeaver/tsconfig": "workspace:*" "@types/react": "npm:^19" @@ -4735,7 +4739,7 @@ __metadata: languageName: unknown linkType: soft -"@codemirror/autocomplete@npm:^6, @codemirror/autocomplete@npm:^6.0.0": +"@codemirror/autocomplete@npm:^6, @codemirror/autocomplete@npm:^6.0.0, @codemirror/autocomplete@npm:^6.20.0": version: 6.20.1 resolution: "@codemirror/autocomplete@npm:6.20.1" dependencies: @@ -4842,7 +4846,7 @@ __metadata: languageName: node linkType: hard -"@codemirror/language@npm:^6, @codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.4.0, @codemirror/language@npm:^6.6.0": +"@codemirror/language@npm:^6, @codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.11.0, @codemirror/language@npm:^6.4.0, @codemirror/language@npm:^6.6.0": version: 6.12.2 resolution: "@codemirror/language@npm:6.12.2" dependencies: @@ -4856,7 +4860,7 @@ __metadata: languageName: node linkType: hard -"@codemirror/lint@npm:^6.0.0": +"@codemirror/lint@npm:^6.0.0, @codemirror/lint@npm:^6.8.5": version: 6.9.5 resolution: "@codemirror/lint@npm:6.9.5" dependencies: @@ -4867,6 +4871,22 @@ __metadata: languageName: node linkType: hard +"@codemirror/lsp-client@npm:^6": + version: 6.2.2 + resolution: "@codemirror/lsp-client@npm:6.2.2" + dependencies: + "@codemirror/autocomplete": "npm:^6.20.0" + "@codemirror/language": "npm:^6.11.0" + "@codemirror/lint": "npm:^6.8.5" + "@codemirror/state": "npm:^6.5.2" + "@codemirror/view": "npm:^6.37.0" + "@lezer/highlight": "npm:^1.2.1" + marked: "npm:^15.0.12" + vscode-languageserver-protocol: "npm:^3.17.5" + checksum: 10c0/909759d953cc189444ce679c6c64859aa1503272510718cb28dea2f7934aa9e8f3985f2f824eac670d1bb211d273753f28b244b4bb0af313f2da058cbbd3cad8 + languageName: node + linkType: hard + "@codemirror/merge@npm:^6": version: 6.12.0 resolution: "@codemirror/merge@npm:6.12.0" @@ -4891,7 +4911,7 @@ __metadata: languageName: node linkType: hard -"@codemirror/state@npm:^6, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0": +"@codemirror/state@npm:^6, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0, @codemirror/state@npm:^6.5.2": version: 6.5.4 resolution: "@codemirror/state@npm:6.5.4" dependencies: @@ -6913,7 +6933,7 @@ __metadata: languageName: node linkType: hard -"@lezer/highlight@npm:^1, @lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3": +"@lezer/highlight@npm:^1, @lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3, @lezer/highlight@npm:^1.2.1": version: 1.2.3 resolution: "@lezer/highlight@npm:1.2.3" dependencies: @@ -14912,6 +14932,15 @@ __metadata: languageName: node linkType: hard +"marked@npm:^15.0.12": + version: 15.0.12 + resolution: "marked@npm:15.0.12" + bin: + marked: bin/marked.js + checksum: 10c0/e09da211544b787ecfb25fed07af206060bf7cd6d9de6cb123f15c496a57f83b7aabea93340aaa94dae9c94e097ae129377cad6310abc16009590972e85f4212 + languageName: node + linkType: hard + "math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" @@ -19701,6 +19730,30 @@ __metadata: languageName: node linkType: hard +"vscode-jsonrpc@npm:8.2.0": + version: 8.2.0 + resolution: "vscode-jsonrpc@npm:8.2.0" + checksum: 10c0/0789c227057a844f5ead55c84679206227a639b9fb76e881185053abc4e9848aa487245966cc2393fcb342c4541241b015a1a2559fddd20ac1e68945c95344e6 + languageName: node + linkType: hard + +"vscode-languageserver-protocol@npm:^3.17.5": + version: 3.17.5 + resolution: "vscode-languageserver-protocol@npm:3.17.5" + dependencies: + vscode-jsonrpc: "npm:8.2.0" + vscode-languageserver-types: "npm:3.17.5" + checksum: 10c0/5f38fd80da9868d706eaa4a025f4aff9c3faad34646bcde1426f915cbd8d7e8b6c3755ce3fef6eebd256ba3145426af1085305f8a76e34276d2e95aaf339a90b + languageName: node + linkType: hard + +"vscode-languageserver-types@npm:3.17.5": + version: 3.17.5 + resolution: "vscode-languageserver-types@npm:3.17.5" + checksum: 10c0/1e1260de79a2cc8de3e46f2e0182cdc94a7eddab487db5a3bd4ee716f67728e685852707d72c059721ce500447be9a46764a04f0611e94e4321ffa088eef36f8 + languageName: node + linkType: hard + "w3c-keyname@npm:^2.2.4": version: 2.2.8 resolution: "w3c-keyname@npm:2.2.8"