@coldsmirk/inkstone-sql 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/dist/assist-gLTZKSD3.d.ts +401 -0
- package/dist/index.d.ts +380 -0
- package/dist/index.js +1531 -0
- package/dist/monaco.d.ts +31 -0
- package/dist/monaco.js +128 -0
- package/dist/react.d.ts +158 -0
- package/dist/react.js +135 -0
- package/package.json +81 -0
package/dist/monaco.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { t as SqlAssist } from "./assist-gLTZKSD3.js";
|
|
2
|
+
import * as monacoEditor from "monaco-editor";
|
|
3
|
+
import { IDisposable } from "monaco-editor";
|
|
4
|
+
|
|
5
|
+
//#region src/monaco.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* The monaco module itself, as `onMount` hands it over — typed structurally so this entry needs
|
|
8
|
+
* no dependency beyond `monaco-editor`'s own types.
|
|
9
|
+
*/
|
|
10
|
+
type MonacoModule = typeof monacoEditor;
|
|
11
|
+
interface SqlLanguageHooks {
|
|
12
|
+
/**
|
|
13
|
+
* The assist as it stands right now — read per request rather than captured, so switching
|
|
14
|
+
* connections swaps the schema behind every feature without touching Monaco's registry.
|
|
15
|
+
*/
|
|
16
|
+
assist: () => SqlAssist | null;
|
|
17
|
+
/**
|
|
18
|
+
* The dialect could not parse the statement, so nothing was reformatted. The host says so:
|
|
19
|
+
* a deliberate keystroke that changes nothing needs to explain itself.
|
|
20
|
+
*/
|
|
21
|
+
onFormatRefused: () => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Register completion, hover, signature help and formatting for one model. The disposables are
|
|
25
|
+
* the caller's to hold: Monaco's registry outlives any editor, and a provider left behind would
|
|
26
|
+
* answer for a model that no longer exists. `languageId` is the language the model is registered
|
|
27
|
+
* under — the stock `"sql"` unless the host runs a dialect id of its own.
|
|
28
|
+
*/
|
|
29
|
+
declare function registerSqlLanguage(monaco: MonacoModule, modelPath: string, hooks: SqlLanguageHooks, languageId?: string): IDisposable[];
|
|
30
|
+
//#endregion
|
|
31
|
+
export { MonacoModule, SqlLanguageHooks, registerSqlLanguage };
|
package/dist/monaco.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
//#region src/monaco.ts
|
|
2
|
+
function itemKinds(monaco) {
|
|
3
|
+
const kinds = monaco.languages.CompletionItemKind;
|
|
4
|
+
return {
|
|
5
|
+
keyword: kinds.Keyword,
|
|
6
|
+
function: kinds.Function,
|
|
7
|
+
schema: kinds.Module,
|
|
8
|
+
table: kinds.Struct,
|
|
9
|
+
view: kinds.Interface,
|
|
10
|
+
column: kinds.Field,
|
|
11
|
+
snippet: kinds.Snippet
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const FORMATTING_KEY = Symbol.for("coldsmirk.inkstone.sql.formattingRegistry");
|
|
15
|
+
function formattingLedgers(monaco) {
|
|
16
|
+
const holder = globalThis;
|
|
17
|
+
const registries = holder[FORMATTING_KEY] ?? /* @__PURE__ */ new WeakMap();
|
|
18
|
+
holder[FORMATTING_KEY] = registries;
|
|
19
|
+
const existing = registries.get(monaco);
|
|
20
|
+
if (existing) return existing;
|
|
21
|
+
const created = /* @__PURE__ */ new Map();
|
|
22
|
+
registries.set(monaco, created);
|
|
23
|
+
return created;
|
|
24
|
+
}
|
|
25
|
+
function registerFormatting(monaco, languageId, modelPath, hooks) {
|
|
26
|
+
const ledgers = formattingLedgers(monaco);
|
|
27
|
+
let ledger = ledgers.get(languageId);
|
|
28
|
+
if (ledger === void 0) {
|
|
29
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
30
|
+
ledger = {
|
|
31
|
+
hooks: byPath,
|
|
32
|
+
registration: monaco.languages.registerDocumentFormattingEditProvider(languageId, { provideDocumentFormattingEdits: async (model) => {
|
|
33
|
+
const owner = byPath.get(model.uri.toString());
|
|
34
|
+
const assist = owner?.assist() ?? null;
|
|
35
|
+
const text = model.getValue();
|
|
36
|
+
if (owner === void 0 || assist === null || text.trim() === "") return [];
|
|
37
|
+
const formatted = await assist.format(text);
|
|
38
|
+
if (formatted === null) {
|
|
39
|
+
owner.onFormatRefused();
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
return formatted === text ? [] : [{
|
|
43
|
+
range: model.getFullModelRange(),
|
|
44
|
+
text: formatted
|
|
45
|
+
}];
|
|
46
|
+
} })
|
|
47
|
+
};
|
|
48
|
+
ledgers.set(languageId, ledger);
|
|
49
|
+
}
|
|
50
|
+
const entered = ledger;
|
|
51
|
+
entered.hooks.set(modelPath, hooks);
|
|
52
|
+
return { dispose: () => {
|
|
53
|
+
entered.hooks.delete(modelPath);
|
|
54
|
+
if (entered.hooks.size === 0) {
|
|
55
|
+
entered.registration.dispose();
|
|
56
|
+
ledgers.delete(languageId);
|
|
57
|
+
}
|
|
58
|
+
} };
|
|
59
|
+
}
|
|
60
|
+
function registerSqlLanguage(monaco, modelPath, hooks, languageId = "sql") {
|
|
61
|
+
const kinds = itemKinds(monaco);
|
|
62
|
+
const path = monaco.Uri.parse(modelPath).toString();
|
|
63
|
+
const answering = (model) => model.uri.toString() === path ? hooks.assist() : null;
|
|
64
|
+
return [
|
|
65
|
+
monaco.languages.registerCompletionItemProvider(languageId, {
|
|
66
|
+
triggerCharacters: ["."],
|
|
67
|
+
provideCompletionItems: async (model, position) => {
|
|
68
|
+
const assist = answering(model);
|
|
69
|
+
if (assist === null) return { suggestions: [] };
|
|
70
|
+
const candidates = await assist.complete(model.getValue(), model.getOffsetAt(position));
|
|
71
|
+
const word = model.getWordUntilPosition(position);
|
|
72
|
+
return { suggestions: candidates.map((candidate, index) => {
|
|
73
|
+
return {
|
|
74
|
+
label: candidate.label,
|
|
75
|
+
kind: kinds[candidate.kind],
|
|
76
|
+
insertText: candidate.insertText,
|
|
77
|
+
insertTextRules: candidate.snippet ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet : void 0,
|
|
78
|
+
detail: candidate.detail,
|
|
79
|
+
documentation: candidate.documentation,
|
|
80
|
+
range: new monaco.Range(position.lineNumber, Math.max(1, word.startColumn - (candidate.replace ?? 0)), position.lineNumber, word.endColumn),
|
|
81
|
+
sortText: `${candidate.sortGroup}:${String(index).padStart(5, "0")}`
|
|
82
|
+
};
|
|
83
|
+
}) };
|
|
84
|
+
}
|
|
85
|
+
}),
|
|
86
|
+
monaco.languages.registerHoverProvider(languageId, { provideHover: async (model, position) => {
|
|
87
|
+
const assist = answering(model);
|
|
88
|
+
if (assist === null) return null;
|
|
89
|
+
const card = await assist.hover(model.getValue(), model.getOffsetAt(position));
|
|
90
|
+
if (card === null) return null;
|
|
91
|
+
const from = model.getPositionAt(card.from);
|
|
92
|
+
const to = model.getPositionAt(card.to);
|
|
93
|
+
return {
|
|
94
|
+
range: new monaco.Range(from.lineNumber, from.column, to.lineNumber, to.column),
|
|
95
|
+
contents: card.markdown.map((value) => {
|
|
96
|
+
return { value };
|
|
97
|
+
})
|
|
98
|
+
};
|
|
99
|
+
} }),
|
|
100
|
+
monaco.languages.registerSignatureHelpProvider(languageId, {
|
|
101
|
+
signatureHelpTriggerCharacters: ["(", ","],
|
|
102
|
+
signatureHelpRetriggerCharacters: [","],
|
|
103
|
+
provideSignatureHelp: async (model, position) => {
|
|
104
|
+
const assist = answering(model);
|
|
105
|
+
if (assist === null) return null;
|
|
106
|
+
const help = await assist.signature(model.getValue(), model.getOffsetAt(position));
|
|
107
|
+
if (help === null) return null;
|
|
108
|
+
return {
|
|
109
|
+
value: {
|
|
110
|
+
signatures: [{
|
|
111
|
+
label: help.label,
|
|
112
|
+
documentation: help.documentation,
|
|
113
|
+
parameters: help.parameters.map((span) => {
|
|
114
|
+
return { label: span };
|
|
115
|
+
})
|
|
116
|
+
}],
|
|
117
|
+
activeSignature: 0,
|
|
118
|
+
activeParameter: help.active
|
|
119
|
+
},
|
|
120
|
+
dispose: () => void 0
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
registerFormatting(monaco, languageId, path, hooks)
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { registerSqlLanguage };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { t as SqlAssist } from "./assist-gLTZKSD3.js";
|
|
2
|
+
import { MonacoModule } from "./monaco.js";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
import { editor } from "monaco-editor";
|
|
5
|
+
|
|
6
|
+
//#region src/react.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* What a host needs to reach into the editor for: inserting at the caret (a schema tree is an
|
|
9
|
+
* input device, not a browser), and reformatting from a toolbar — which goes through Monaco's own
|
|
10
|
+
* formatting action, so the button and `Shift+Alt+F` are one path and one undo.
|
|
11
|
+
*/
|
|
12
|
+
interface SqlEditorHandle {
|
|
13
|
+
insert: (text: string) => void;
|
|
14
|
+
format: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Where the operator is in the document, in offsets — everything a host needs to decide which
|
|
18
|
+
* statement a run should send, and nothing about SQL. `from` and `to` are the selection's own
|
|
19
|
+
* bounds and are equal where nothing is selected: a caret is a selection of nothing.
|
|
20
|
+
*
|
|
21
|
+
* It rides with the text rather than beside it because the two must belong to the same instant.
|
|
22
|
+
* The controlled `value` is one React render behind the keystroke that triggered a run, and a
|
|
23
|
+
* console must run the statement the operator is looking at.
|
|
24
|
+
*/
|
|
25
|
+
interface SqlCaret {
|
|
26
|
+
text: string;
|
|
27
|
+
from: number;
|
|
28
|
+
to: number;
|
|
29
|
+
}
|
|
30
|
+
interface SqlEditorProps {
|
|
31
|
+
value: string;
|
|
32
|
+
onChange: (value: string) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Render with the dark theme. Like every inkstone editor, the component never reads the OS
|
|
35
|
+
* `prefers-color-scheme`; wire this to the app's color-scheme state.
|
|
36
|
+
*
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
dark?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Editor height in px, or `"fill"` to consume the parent's resolved height (the parent must be
|
|
42
|
+
* a `min-height: 0` flex item). Monaco's loading placeholder is `height: 100%`, so a height
|
|
43
|
+
* that resolves to nothing collapses the field to one line while the editor loads.
|
|
44
|
+
*/
|
|
45
|
+
height?: number | "fill";
|
|
46
|
+
/**
|
|
47
|
+
* Editor font size in px. Omit to keep the inkstone default.
|
|
48
|
+
*/
|
|
49
|
+
fontSize?: number;
|
|
50
|
+
/**
|
|
51
|
+
* UI locale for Monaco's built-in chrome — page-global and locked by the first editor to
|
|
52
|
+
* mount, exactly as on `<MonacoEditor>`.
|
|
53
|
+
*/
|
|
54
|
+
locale?: "en" | "zh-cn";
|
|
55
|
+
placeholder?: string;
|
|
56
|
+
/**
|
|
57
|
+
* The editor's accessible name, set on Monaco's own input surface — several SQL editors on one
|
|
58
|
+
* page are indistinguishable to assistive tech without one.
|
|
59
|
+
*/
|
|
60
|
+
ariaLabel: string;
|
|
61
|
+
/**
|
|
62
|
+
* The Monaco language id the model registers under.
|
|
63
|
+
*
|
|
64
|
+
* @default "sql"
|
|
65
|
+
*/
|
|
66
|
+
languageId?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Shown while the Monaco host loads / when it fails to load — passed through to
|
|
69
|
+
* `<MonacoEditor>`, whose English defaults apply when omitted.
|
|
70
|
+
*/
|
|
71
|
+
loading?: ReactNode;
|
|
72
|
+
failure?: ReactNode;
|
|
73
|
+
/**
|
|
74
|
+
* Ctrl/Cmd-Enter — the run key, bound inside the editor because that is where the operator's
|
|
75
|
+
* hands are. It is handed the editor's live caret, from which the host works out what to send.
|
|
76
|
+
* Read through a ref at every press, so it always runs against the connection the *current*
|
|
77
|
+
* render is bound to.
|
|
78
|
+
*/
|
|
79
|
+
onRun?: (caret: SqlCaret) => void;
|
|
80
|
+
/**
|
|
81
|
+
* The label the run action shows in Monaco's own chrome (command palette, context menu).
|
|
82
|
+
*
|
|
83
|
+
* @default "Run statement"
|
|
84
|
+
*/
|
|
85
|
+
runActionLabel?: string;
|
|
86
|
+
/**
|
|
87
|
+
* The caret moved, or the text under it changed. The host tracks it to show what a run would
|
|
88
|
+
* send; the run key does not depend on that tracking having caught up.
|
|
89
|
+
*/
|
|
90
|
+
onCaret?: (caret: SqlCaret) => void;
|
|
91
|
+
/**
|
|
92
|
+
* The span to mark as the one a run would send. Null leaves the gutter clean, which is the
|
|
93
|
+
* right answer while there is nothing to choose between.
|
|
94
|
+
*/
|
|
95
|
+
highlight?: {
|
|
96
|
+
from: number;
|
|
97
|
+
to: number;
|
|
98
|
+
} | null;
|
|
99
|
+
/**
|
|
100
|
+
* The class the highlight's line decoration carries. The host owns its stylesheet — a
|
|
101
|
+
* left-margin bar reads well, because it says which statement without touching a single colour
|
|
102
|
+
* the syntax already assigned.
|
|
103
|
+
*
|
|
104
|
+
* @default "inkstone-sql-runnable"
|
|
105
|
+
*/
|
|
106
|
+
highlightClassName?: string;
|
|
107
|
+
/**
|
|
108
|
+
* The dialect could not parse the statement, so the format action changed nothing. The host
|
|
109
|
+
* says so — a deliberate keystroke that changes nothing needs to explain itself.
|
|
110
|
+
*/
|
|
111
|
+
onFormatRefused?: () => void;
|
|
112
|
+
onReady?: (handle: SqlEditorHandle) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Runs after this component's own mount wiring, with the live editor and the monaco module —
|
|
115
|
+
* the escape hatch for host-specific concerns (focus shields, extra actions).
|
|
116
|
+
*/
|
|
117
|
+
onMount?: (instance: editor.IStandaloneCodeEditor, monaco: MonacoModule) => void;
|
|
118
|
+
/**
|
|
119
|
+
* What the editor's language features answer from — the schema and dialect of the connection
|
|
120
|
+
* being edited (`createSqlAssist`). Read through a ref at every request, so the host can swap
|
|
121
|
+
* it without re-registering anything with Monaco.
|
|
122
|
+
*/
|
|
123
|
+
assist?: SqlAssist;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The SQL editing surface: Monaco in `sql`, highlighted by the same shared Shiki grammar every
|
|
127
|
+
* other inkstone surface uses. It is deliberately not a script editor — a TypeScript language
|
|
128
|
+
* service and an ambient profile have no meaning for a vendor's SQL. Its language features are
|
|
129
|
+
* the host's: the `assist` knows the catalog and the dialect and this component only wires it to
|
|
130
|
+
* Monaco (`registerSqlLanguage`) — it never guesses at SQL itself.
|
|
131
|
+
*
|
|
132
|
+
* The editor draws no chrome of its own: it is a band of its host's surface, and the separation
|
|
133
|
+
* is the host's hairline rules — a border here would dress a pane up as a form input.
|
|
134
|
+
*/
|
|
135
|
+
declare function SqlEditor({
|
|
136
|
+
value,
|
|
137
|
+
onChange,
|
|
138
|
+
dark,
|
|
139
|
+
height,
|
|
140
|
+
fontSize,
|
|
141
|
+
locale,
|
|
142
|
+
placeholder,
|
|
143
|
+
ariaLabel,
|
|
144
|
+
languageId,
|
|
145
|
+
loading,
|
|
146
|
+
failure,
|
|
147
|
+
onRun,
|
|
148
|
+
runActionLabel,
|
|
149
|
+
onCaret,
|
|
150
|
+
highlight,
|
|
151
|
+
highlightClassName,
|
|
152
|
+
onFormatRefused,
|
|
153
|
+
onReady,
|
|
154
|
+
onMount,
|
|
155
|
+
assist
|
|
156
|
+
}: SqlEditorProps): import("react").JSX.Element;
|
|
157
|
+
//#endregion
|
|
158
|
+
export { SqlCaret, SqlEditor, SqlEditorHandle, SqlEditorProps };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { registerSqlLanguage } from "./monaco.js";
|
|
2
|
+
import { MonacoEditor } from "@coldsmirk/inkstone-react/monaco";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/react.tsx
|
|
6
|
+
const EDITOR_ID_KEY = Symbol.for("coldsmirk.inkstone.sql.editorId");
|
|
7
|
+
function nextEditorId() {
|
|
8
|
+
const holder = globalThis;
|
|
9
|
+
const id = holder[EDITOR_ID_KEY] ?? 0;
|
|
10
|
+
holder[EDITOR_ID_KEY] = id + 1;
|
|
11
|
+
return id;
|
|
12
|
+
}
|
|
13
|
+
const FORMAT_CHORD_CONTEXT_KEY = "inkstoneSqlFormatChord";
|
|
14
|
+
function useLatest(value) {
|
|
15
|
+
const ref = useRef(value);
|
|
16
|
+
useInsertionEffect(() => {
|
|
17
|
+
ref.current = value;
|
|
18
|
+
});
|
|
19
|
+
return ref;
|
|
20
|
+
}
|
|
21
|
+
function caretOf(instance) {
|
|
22
|
+
const model = instance.getModel();
|
|
23
|
+
const selection = instance.getSelection();
|
|
24
|
+
if (model === null || selection === null) return {
|
|
25
|
+
text: "",
|
|
26
|
+
from: 0,
|
|
27
|
+
to: 0
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
text: model.getValue(),
|
|
31
|
+
from: model.getOffsetAt(selection.getStartPosition()),
|
|
32
|
+
to: model.getOffsetAt(selection.getEndPosition())
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function monacoRange(model, span) {
|
|
36
|
+
const from = model.getPositionAt(span.from);
|
|
37
|
+
const to = model.getPositionAt(span.to);
|
|
38
|
+
return {
|
|
39
|
+
startLineNumber: from.lineNumber,
|
|
40
|
+
startColumn: from.column,
|
|
41
|
+
endLineNumber: to.lineNumber,
|
|
42
|
+
endColumn: to.column
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function SqlEditor({ value, onChange, dark = false, height = 200, fontSize, locale, placeholder, ariaLabel, languageId = "sql", loading, failure, onRun, runActionLabel = "Run statement", onCaret, highlight = null, highlightClassName = "inkstone-sql-runnable", onFormatRefused, onReady, onMount, assist }) {
|
|
46
|
+
const [modelPath] = useState(() => `file:///inkstone-sql/${nextEditorId()}.sql`);
|
|
47
|
+
const bindings = useRef([]);
|
|
48
|
+
const assistRef = useLatest(assist ?? null);
|
|
49
|
+
const formatRefusedRef = useLatest(onFormatRefused);
|
|
50
|
+
const runRef = useLatest(onRun);
|
|
51
|
+
const caretChangedRef = useLatest(onCaret);
|
|
52
|
+
const readyRef = useLatest(onReady);
|
|
53
|
+
const mountRef = useLatest(onMount);
|
|
54
|
+
const instance = useRef(null);
|
|
55
|
+
const marked = useRef(null);
|
|
56
|
+
const [mounted, setMounted] = useState(false);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const model = instance.current?.getModel();
|
|
59
|
+
if (model === null || model === void 0) return;
|
|
60
|
+
marked.current?.clear();
|
|
61
|
+
marked.current = highlight === null ? null : instance.current.createDecorationsCollection([{
|
|
62
|
+
range: monacoRange(model, highlight),
|
|
63
|
+
options: {
|
|
64
|
+
linesDecorationsClassName: highlightClassName,
|
|
65
|
+
isWholeLine: true
|
|
66
|
+
}
|
|
67
|
+
}]);
|
|
68
|
+
}, [
|
|
69
|
+
highlight,
|
|
70
|
+
highlightClassName,
|
|
71
|
+
mounted
|
|
72
|
+
]);
|
|
73
|
+
useEffect(() => () => {
|
|
74
|
+
for (const binding of bindings.current) binding.dispose();
|
|
75
|
+
bindings.current = [];
|
|
76
|
+
}, []);
|
|
77
|
+
const fill = height === "fill";
|
|
78
|
+
return /* @__PURE__ */ jsx("div", {
|
|
79
|
+
style: fill ? {
|
|
80
|
+
height: "100%",
|
|
81
|
+
minHeight: 0
|
|
82
|
+
} : { height },
|
|
83
|
+
children: /* @__PURE__ */ jsx(MonacoEditor, {
|
|
84
|
+
showLineNumbers: true,
|
|
85
|
+
dark,
|
|
86
|
+
failure,
|
|
87
|
+
fontSize,
|
|
88
|
+
height: fill ? "100%" : height,
|
|
89
|
+
language: languageId,
|
|
90
|
+
loading,
|
|
91
|
+
locale,
|
|
92
|
+
path: modelPath,
|
|
93
|
+
placeholder,
|
|
94
|
+
value,
|
|
95
|
+
options: {
|
|
96
|
+
ariaLabel,
|
|
97
|
+
suggest: { snippetsPreventQuickSuggestions: false }
|
|
98
|
+
},
|
|
99
|
+
onChange,
|
|
100
|
+
onMount: (live, monaco) => {
|
|
101
|
+
instance.current = live;
|
|
102
|
+
live.createContextKey(FORMAT_CHORD_CONTEXT_KEY, true);
|
|
103
|
+
bindings.current.push(live.onDidChangeCursorSelection(() => caretChangedRef.current?.(caretOf(live))), live.addAction({
|
|
104
|
+
id: "inkstone-sql.run",
|
|
105
|
+
label: runActionLabel,
|
|
106
|
+
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
|
|
107
|
+
run: (current) => runRef.current?.(caretOf(current))
|
|
108
|
+
}), monaco.editor.addKeybindingRule({
|
|
109
|
+
keybinding: monaco.KeyMod.Shift | monaco.KeyMod.Alt | monaco.KeyCode.KeyF,
|
|
110
|
+
command: "editor.action.formatDocument",
|
|
111
|
+
when: `${FORMAT_CHORD_CONTEXT_KEY} && editorTextFocus && !editorReadonly`
|
|
112
|
+
}), ...registerSqlLanguage(monaco, modelPath, {
|
|
113
|
+
assist: () => assistRef.current,
|
|
114
|
+
onFormatRefused: () => formatRefusedRef.current?.()
|
|
115
|
+
}, languageId));
|
|
116
|
+
readyRef.current?.({
|
|
117
|
+
format: () => void live.getAction("editor.action.formatDocument")?.run(),
|
|
118
|
+
insert: (text) => {
|
|
119
|
+
const selection = live.getSelection();
|
|
120
|
+
if (selection) live.executeEdits("inkstone-sql-insert", [{
|
|
121
|
+
range: selection,
|
|
122
|
+
text,
|
|
123
|
+
forceMoveMarkers: true
|
|
124
|
+
}]);
|
|
125
|
+
live.focus();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
mountRef.current?.(live, monaco);
|
|
129
|
+
setMounted(true);
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { SqlEditor };
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coldsmirk/inkstone-sql",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "Schema-aware SQL editing intelligence for Monaco: lexical statement reading under per-dialect noise profiles, catalog-driven completion/hover/signature help, unified-placeholder awareness, formatting, and a drop-in React SQL editor.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"sql",
|
|
7
|
+
"monaco",
|
|
8
|
+
"completion",
|
|
9
|
+
"editor",
|
|
10
|
+
"autocomplete",
|
|
11
|
+
"dialect"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/coldsmirk/inkstone/tree/main/packages/sql#readme",
|
|
14
|
+
"bugs": "https://github.com/coldsmirk/inkstone/issues",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/coldsmirk/inkstone.git",
|
|
18
|
+
"directory": "packages/sql"
|
|
19
|
+
},
|
|
20
|
+
"license": "UNLICENSED",
|
|
21
|
+
"author": {
|
|
22
|
+
"name": "Venus"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./monaco": {
|
|
32
|
+
"types": "./dist/monaco.d.ts",
|
|
33
|
+
"default": "./dist/monaco.js"
|
|
34
|
+
},
|
|
35
|
+
"./react": {
|
|
36
|
+
"types": "./dist/react.d.ts",
|
|
37
|
+
"default": "./dist/react.js"
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"files": [
|
|
43
|
+
"dist"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"sql-formatter": "^15.8.2"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"monaco-editor": "^0.55.1",
|
|
50
|
+
"react": "^19.2.7",
|
|
51
|
+
"react-dom": "^19.2.7",
|
|
52
|
+
"@coldsmirk/inkstone-react": "^0.13.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"monaco-editor": "^0.55.1",
|
|
56
|
+
"react": ">=19",
|
|
57
|
+
"@coldsmirk/inkstone-react": "^0.13.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@coldsmirk/inkstone-react": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"monaco-editor": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"react": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=24"
|
|
72
|
+
},
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public"
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "tsdown",
|
|
78
|
+
"clean": "rimraf dist",
|
|
79
|
+
"typecheck": "tsc --noEmit"
|
|
80
|
+
}
|
|
81
|
+
}
|