Line data Source code
1 : import * as dashql from '@ankoh/dashql-core';
2 :
3 : import type { EditorView } from '@codemirror/view';
4 :
5 : import type { ScriptData } from './session_scripts.js';
6 : import type { AppSettings } from '../../config/app_config.js';
7 : import { ConnectorType } from '../../connections/connector_info.js';
8 :
9 1 : const DEFAULT_FORMAT_WIDTH = 80;
10 1 : const DEFAULT_FORMAT_INDENTATION = 4;
11 1 : const MIN_FORMAT_WIDTH = 24;
12 :
13 1 : export function formattingDialectForConnector(connectorType?: ConnectorType): dashql.buffers.formatting.FormattingDialect {
14 1 : return connectorType === ConnectorType.TRINO
15 1 : ? dashql.buffers.formatting.FormattingDialect.TRINO
16 1 : : dashql.buffers.formatting.FormattingDialect.HYPER;
17 1 : }
18 :
19 1 : export function getKeywordCase(settings?: AppSettings): dashql.buffers.formatting.KeywordCase {
20 1 : return settings?.keywordCase === 'upper'
21 1 : ? dashql.buffers.formatting.KeywordCase.UPPER
22 1 : : dashql.buffers.formatting.KeywordCase.LOWER;
23 1 : }
24 :
25 1 : export function createScriptFormatConfig(
26 1 : mode: dashql.buffers.formatting.FormattingMode,
27 1 : debugMode: boolean = false,
28 1 : maxWidth: number = DEFAULT_FORMAT_WIDTH,
29 1 : keywordCase: dashql.buffers.formatting.KeywordCase = dashql.buffers.formatting.KeywordCase.LOWER,
30 1 : dialect: dashql.buffers.formatting.FormattingDialect = dashql.buffers.formatting.FormattingDialect.HYPER,
31 1 : ): dashql.buffers.formatting.FormattingConfigT {
32 1 : return new dashql.buffers.formatting.FormattingConfigT(
33 1 : dialect,
34 1 : mode,
35 1 : maxWidth,
36 1 : DEFAULT_FORMAT_INDENTATION,
37 1 : debugMode,
38 1 : keywordCase,
39 1 : );
40 1 : }
41 :
42 : /// Format a SQL text in a throwaway script, null if the formatter cannot format all of it
43 1 : export function formatSqlText(core: dashql.DashQL, text: string, config: dashql.buffers.formatting.FormattingConfigT): string | null {
44 1 : const catalog = core.createCatalog();
45 1 : const script = core.createScript(catalog);
46 1 : let formatted: dashql.DashQLScript | null = null;
47 1 : try {
48 1 : script.insertTextAt(0, text);
49 1 : if (!script.isFullyFormattable(config)) return null;
50 1 : formatted = script.format(config);
51 1 : return formatted.toString();
52 1 : } catch {
53 0 : return null;
54 1 : } finally {
55 1 : formatted?.destroy();
56 1 : script.destroy();
57 1 : catalog.destroy();
58 1 : }
59 1 : }
60 :
61 1 : export function measureScriptFormatWidth(editorView: Pick<EditorView, 'defaultCharacterWidth' | 'scrollDOM'>): number {
62 1 : const charWidth = editorView.defaultCharacterWidth;
63 1 : const availableWidth = editorView.scrollDOM.clientWidth;
64 1 : if (!(charWidth > 0) || !(availableWidth > 0)) {
65 0 : return DEFAULT_FORMAT_WIDTH;
66 : }
67 1 : return Math.max(MIN_FORMAT_WIDTH, Math.floor(availableWidth / charWidth));
68 1 : }
69 :
70 1 : export function isScriptFormattable(
71 1 : scriptData: ScriptData | null,
72 1 : dialect: dashql.buffers.formatting.FormattingDialect = dashql.buffers.formatting.FormattingDialect.HYPER,
73 1 : ): boolean {
74 1 : if (scriptData == null) return false;
75 1 : try {
76 1 : return scriptData.scriptSession.isFullyFormattable(
77 1 : createScriptFormatConfig(dashql.buffers.formatting.FormattingMode.PRETTY, false, DEFAULT_FORMAT_WIDTH,
78 1 : dashql.buffers.formatting.KeywordCase.LOWER, dialect),
79 1 : true,
80 1 : );
81 1 : } catch {
82 1 : return false;
83 1 : }
84 1 : }
85 :
86 1 : export function formatScriptEditor(
87 1 : editorView: EditorView | null,
88 1 : scriptData: ScriptData | null,
89 1 : mode: dashql.buffers.formatting.FormattingMode,
90 1 : onFormattedText: (text: string) => void,
91 1 : debugMode: boolean = false,
92 1 : keywordCase: dashql.buffers.formatting.KeywordCase = dashql.buffers.formatting.KeywordCase.LOWER,
93 1 : dialect: dashql.buffers.formatting.FormattingDialect = dashql.buffers.formatting.FormattingDialect.HYPER,
94 1 : ): boolean {
95 1 : if (editorView == null || scriptData == null) return false;
96 :
97 1 : let formattedScript: dashql.DashQLScript | null = null;
98 1 : try {
99 1 : const maxWidth = measureScriptFormatWidth(editorView);
100 1 : formattedScript = scriptData.scriptSession.format(
101 1 : createScriptFormatConfig(mode, debugMode, maxWidth, keywordCase, dialect),
102 1 : null,
103 1 : );
104 1 : const formattedText = formattedScript.toString();
105 1 : if (formattedText === editorView.state.doc.toString()) return false;
106 1 : onFormattedText(formattedText);
107 1 : editorView.focus();
108 1 : return true;
109 1 : } catch {
110 0 : return false;
111 1 : } finally {
112 1 : formattedScript?.destroy();
113 1 : }
114 1 : }
|