niuma-ui 1.0.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.
Files changed (220) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +25 -0
  4. package/README.en.md +99 -0
  5. package/README.md +161 -0
  6. package/package.json +106 -0
  7. package/src/__tests__/RsBadge.spec.ts +40 -0
  8. package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
  9. package/src/__tests__/RsButton.spec.ts +108 -0
  10. package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
  11. package/src/__tests__/RsCard.spec.ts +97 -0
  12. package/src/__tests__/RsCheckbox.spec.ts +36 -0
  13. package/src/__tests__/RsCodeEditor.spec.ts +111 -0
  14. package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
  15. package/src/__tests__/RsContainer.spec.ts +89 -0
  16. package/src/__tests__/RsContextMenu.spec.ts +118 -0
  17. package/src/__tests__/RsDatePicker.spec.ts +88 -0
  18. package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
  19. package/src/__tests__/RsDialog.spec.ts +404 -0
  20. package/src/__tests__/RsDrawer.spec.ts +145 -0
  21. package/src/__tests__/RsDropdown.spec.ts +135 -0
  22. package/src/__tests__/RsEmpty.spec.ts +99 -0
  23. package/src/__tests__/RsForm.spec.ts +173 -0
  24. package/src/__tests__/RsIcon.spec.ts +244 -0
  25. package/src/__tests__/RsInput.spec.ts +269 -0
  26. package/src/__tests__/RsInputNumber.spec.ts +118 -0
  27. package/src/__tests__/RsLabel.spec.ts +106 -0
  28. package/src/__tests__/RsLink.spec.ts +49 -0
  29. package/src/__tests__/RsLoading.spec.ts +84 -0
  30. package/src/__tests__/RsMarkdown.spec.ts +143 -0
  31. package/src/__tests__/RsMenu.spec.ts +119 -0
  32. package/src/__tests__/RsNewComponents.spec.ts +17 -0
  33. package/src/__tests__/RsPagination.spec.ts +158 -0
  34. package/src/__tests__/RsPopover.spec.ts +126 -0
  35. package/src/__tests__/RsScrollbar.spec.ts +53 -0
  36. package/src/__tests__/RsSelect.spec.ts +291 -0
  37. package/src/__tests__/RsSidebar.spec.ts +119 -0
  38. package/src/__tests__/RsSplitPane.spec.ts +177 -0
  39. package/src/__tests__/RsStatCard.spec.ts +79 -0
  40. package/src/__tests__/RsSteps.spec.ts +125 -0
  41. package/src/__tests__/RsTable.perf.spec.ts +103 -0
  42. package/src/__tests__/RsTable.spec.ts +1135 -0
  43. package/src/__tests__/RsTabs.spec.ts +328 -0
  44. package/src/__tests__/RsTerminal.spec.ts +218 -0
  45. package/src/__tests__/RsTimePicker.spec.ts +77 -0
  46. package/src/__tests__/RsToaster.spec.ts +95 -0
  47. package/src/__tests__/RsToolbar.spec.ts +75 -0
  48. package/src/__tests__/RsTooltip.spec.ts +111 -0
  49. package/src/__tests__/RsTree.spec.ts +572 -0
  50. package/src/__tests__/RsUpload.spec.ts +160 -0
  51. package/src/__tests__/RsVirtualList.spec.ts +77 -0
  52. package/src/__tests__/date-picker-utils.spec.ts +70 -0
  53. package/src/__tests__/dialog-viewport.spec.ts +38 -0
  54. package/src/__tests__/iso-local-datetime.spec.ts +58 -0
  55. package/src/__tests__/rs-clipboard.spec.ts +142 -0
  56. package/src/__tests__/setup.ts +12 -0
  57. package/src/__tests__/split-pane-utils.spec.ts +136 -0
  58. package/src/__tests__/table-drag.spec.ts +35 -0
  59. package/src/__tests__/table-edit-utils.spec.ts +194 -0
  60. package/src/__tests__/table-utils.spec.ts +286 -0
  61. package/src/__tests__/theme.spec.ts +22 -0
  62. package/src/__tests__/tree-utils.spec.ts +234 -0
  63. package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
  64. package/src/components/RsAvatar.vue +115 -0
  65. package/src/components/RsBadge.vue +59 -0
  66. package/src/components/RsBreadcrumb.vue +65 -0
  67. package/src/components/RsBreadcrumbItems.vue +36 -0
  68. package/src/components/RsButton.vue +393 -0
  69. package/src/components/RsCalendarGrid.vue +284 -0
  70. package/src/components/RsCard.vue +237 -0
  71. package/src/components/RsCheckbox.vue +189 -0
  72. package/src/components/RsCodeBlock.vue +278 -0
  73. package/src/components/RsCodeEditor.vue +597 -0
  74. package/src/components/RsConfigProvider.vue +92 -0
  75. package/src/components/RsConfirmDialog.vue +178 -0
  76. package/src/components/RsContainer.vue +235 -0
  77. package/src/components/RsContextMenu.vue +238 -0
  78. package/src/components/RsContextMenuItems.vue +77 -0
  79. package/src/components/RsDatePicker.vue +756 -0
  80. package/src/components/RsDateTimePicker.vue +42 -0
  81. package/src/components/RsDialog.vue +486 -0
  82. package/src/components/RsDrawer.vue +175 -0
  83. package/src/components/RsDropdown.vue +252 -0
  84. package/src/components/RsDropdownItems.vue +90 -0
  85. package/src/components/RsEmpty.vue +107 -0
  86. package/src/components/RsForm.vue +131 -0
  87. package/src/components/RsIcon.vue +104 -0
  88. package/src/components/RsInput.vue +1101 -0
  89. package/src/components/RsInputNumber.vue +532 -0
  90. package/src/components/RsLabel.vue +54 -0
  91. package/src/components/RsLink.vue +106 -0
  92. package/src/components/RsLoading.vue +171 -0
  93. package/src/components/RsMarkdown.vue +329 -0
  94. package/src/components/RsMenu.vue +256 -0
  95. package/src/components/RsMenuItems.vue +125 -0
  96. package/src/components/RsMonacoEditor.vue +541 -0
  97. package/src/components/RsPagination.vue +184 -0
  98. package/src/components/RsPopover.vue +77 -0
  99. package/src/components/RsProseEditor.vue +160 -0
  100. package/src/components/RsScrollbar.vue +171 -0
  101. package/src/components/RsSelect.vue +1045 -0
  102. package/src/components/RsSidebar.vue +121 -0
  103. package/src/components/RsSidebarGroup.vue +32 -0
  104. package/src/components/RsSidebarItem.vue +72 -0
  105. package/src/components/RsSplitPane.vue +619 -0
  106. package/src/components/RsStatCard.vue +85 -0
  107. package/src/components/RsSteps.vue +138 -0
  108. package/src/components/RsTable.vue +2664 -0
  109. package/src/components/RsTableColgroup.vue +17 -0
  110. package/src/components/RsTableHeaderRow.vue +30 -0
  111. package/src/components/RsTabs.vue +951 -0
  112. package/src/components/RsTerminal.vue +697 -0
  113. package/src/components/RsTimePicker.vue +554 -0
  114. package/src/components/RsTimePickerColumns.vue +226 -0
  115. package/src/components/RsToaster.vue +396 -0
  116. package/src/components/RsToolbar.vue +135 -0
  117. package/src/components/RsTooltip.vue +142 -0
  118. package/src/components/RsTooltipProvider.vue +20 -0
  119. package/src/components/RsTree.vue +1248 -0
  120. package/src/components/RsUpload.vue +279 -0
  121. package/src/components/RsVirtualList.vue +217 -0
  122. package/src/components/avatar-utils.ts +19 -0
  123. package/src/components/breadcrumb-utils.ts +19 -0
  124. package/src/components/class-utils.ts +29 -0
  125. package/src/components/code-editor-utils.ts +106 -0
  126. package/src/components/code-mirror-completion.ts +40 -0
  127. package/src/components/code-mirror-diagnostics.ts +52 -0
  128. package/src/components/code-mirror-ghost.ts +158 -0
  129. package/src/components/code-mirror-inline-edit.ts +55 -0
  130. package/src/components/code-mirror-intel.ts +81 -0
  131. package/src/components/code-mirror-lang.ts +128 -0
  132. package/src/components/context-menu-utils.ts +20 -0
  133. package/src/components/date-picker-utils.ts +284 -0
  134. package/src/components/date-range-validation.ts +29 -0
  135. package/src/components/date-validation.ts +38 -0
  136. package/src/components/dialog-viewport.ts +48 -0
  137. package/src/components/dialog-window.ts +253 -0
  138. package/src/components/dropdown-utils.ts +31 -0
  139. package/src/components/form-utils.ts +77 -0
  140. package/src/components/input-number-utils.ts +128 -0
  141. package/src/components/input-rules.ts +84 -0
  142. package/src/components/loading-utils.ts +8 -0
  143. package/src/components/markdown-utils.ts +126 -0
  144. package/src/components/menu-utils.ts +48 -0
  145. package/src/components/overlay-utils.ts +47 -0
  146. package/src/components/pagination-utils.ts +38 -0
  147. package/src/components/popover-utils.ts +8 -0
  148. package/src/components/reka.ts +106 -0
  149. package/src/components/resolve-radius.ts +22 -0
  150. package/src/components/resolve-size.ts +29 -0
  151. package/src/components/scrollbar-utils.ts +10 -0
  152. package/src/components/select-utils.ts +72 -0
  153. package/src/components/split-pane-utils.ts +187 -0
  154. package/src/components/steps-utils.ts +22 -0
  155. package/src/components/table/RsTableBodyRow.vue +287 -0
  156. package/src/components/table/RsTableCell.vue +239 -0
  157. package/src/components/table/RsTableCellContent.ts +18 -0
  158. package/src/components/table/RsTableCellEditor.vue +351 -0
  159. package/src/components/table/RsTableEditGutter.vue +54 -0
  160. package/src/components/table/RsTableHeaderFilter.vue +107 -0
  161. package/src/components/table/RsTableRowStatus.vue +69 -0
  162. package/src/components/table/rs-table.css +1141 -0
  163. package/src/components/table/table-context-menu.ts +53 -0
  164. package/src/components/table/table-edit-utils.ts +496 -0
  165. package/src/components/table/table-keys.ts +12 -0
  166. package/src/components/table-drag.ts +215 -0
  167. package/src/components/table-utils.ts +950 -0
  168. package/src/components/tabs-utils.ts +107 -0
  169. package/src/components/terminal-utils.ts +301 -0
  170. package/src/components/terminal-wheel.ts +49 -0
  171. package/src/components/time-picker-utils.ts +167 -0
  172. package/src/components/time-range-validation.ts +23 -0
  173. package/src/components/time-validation.ts +20 -0
  174. package/src/components/tree-utils.ts +518 -0
  175. package/src/components/upload-utils.ts +55 -0
  176. package/src/components/virtual-list-utils.ts +17 -0
  177. package/src/composables/useClickOutside.ts +22 -0
  178. package/src/composables/useRsConfig.ts +91 -0
  179. package/src/composables/useRsI18n.ts +22 -0
  180. package/src/composables/useRsTabsNav.ts +187 -0
  181. package/src/composables/useRsToast.ts +48 -0
  182. package/src/composables/useTableCellTooltip.ts +239 -0
  183. package/src/composables/useTableEdit.ts +271 -0
  184. package/src/composables/useTableRowHighlight.ts +50 -0
  185. package/src/dev/vite-codemirror-deps.ts +20 -0
  186. package/src/icons/custom/api.ts +54 -0
  187. package/src/icons/custom/clickhouse.ts +52 -0
  188. package/src/icons/custom/dameng.ts +83 -0
  189. package/src/icons/custom/ftp.ts +61 -0
  190. package/src/icons/custom/kingbase.ts +52 -0
  191. package/src/icons/custom/mongodb.ts +50 -0
  192. package/src/icons/custom/mysql.ts +51 -0
  193. package/src/icons/custom/oracle.ts +52 -0
  194. package/src/icons/custom/redis.ts +51 -0
  195. package/src/icons/custom/sqlite.ts +50 -0
  196. package/src/icons/custom/sqlserver.ts +52 -0
  197. package/src/icons/custom/vastbase.ts +57 -0
  198. package/src/icons/lucide.ts +78 -0
  199. package/src/icons/registry.ts +35 -0
  200. package/src/index.ts +425 -0
  201. package/src/lib/iso-local-datetime.ts +152 -0
  202. package/src/lib/rs-dayjs.ts +56 -0
  203. package/src/locale/messages.ts +362 -0
  204. package/src/locale/types.ts +5 -0
  205. package/src/monaco/debug-decorations.css +20 -0
  206. package/src/monaco/debug-decorations.ts +70 -0
  207. package/src/monaco/index.ts +22 -0
  208. package/src/monaco/languages/index.ts +21 -0
  209. package/src/monaco/languages/mongodb-shell.ts +125 -0
  210. package/src/monaco/languages/types.ts +55 -0
  211. package/src/monaco/setup.ts +101 -0
  212. package/src/styles.css +1040 -0
  213. package/src/theme/apply.ts +12 -0
  214. package/src/theme/brand.example.css +37 -0
  215. package/src/theme/presets.ts +69 -0
  216. package/src/theme/types.ts +81 -0
  217. package/src/utils/rs-clipboard.ts +272 -0
  218. package/src/vite-env.d.ts +7 -0
  219. package/vite-plugins/monaco-zh-nls.ts +101 -0
  220. package/vite-plugins/silence-antlr-parse-console.ts +29 -0
@@ -0,0 +1,541 @@
1
+ <script setup lang="ts">
2
+ import * as monaco from 'monaco-editor'
3
+ import { onMounted, onUnmounted, ref, watch } from 'vue'
4
+ import { applyMonacoDebugDecorations } from '../monaco/debug-decorations'
5
+ import type { MonacoLanguage } from '../monaco/languages'
6
+ import {
7
+ ensureMongodbShellLanguage,
8
+ MONACO_MONGODB_SHELL_LANGUAGE,
9
+ MONACO_MYSQL_LANGUAGE,
10
+ MONACO_DAMENG_LANGUAGE,
11
+ MONACO_KINGBASE_LANGUAGE,
12
+ } from '../monaco/languages'
13
+ /** VS Code 式调试装饰样式(断点 / 当前行);业务组件勿再自定义 glyph CSS */
14
+ import '../monaco/debug-decorations.css'
15
+
16
+ /** 实例级补全片段(仅作用于当前编辑器 Model)。 */
17
+ export interface MonacoCompletionSnippet {
18
+ label: string
19
+ insertText: string
20
+ detail?: string
21
+ documentation?: string
22
+ filterText?: string
23
+ sortText?: string
24
+ kind?: 'snippet' | 'keyword' | 'property' | 'field' | 'function' | 'value'
25
+ preselect?: boolean
26
+ }
27
+
28
+ /** MonacoCompletionContext 描述一次补全请求的编辑器上下文。 */
29
+ export interface MonacoCompletionContext {
30
+ text: string
31
+ offset: number
32
+ line: number
33
+ column: number
34
+ prefix: string
35
+ triggerCharacter?: string
36
+ }
37
+
38
+ /** MonacoCompletionRequest 按当前文本和光标位置返回实例级候选。 */
39
+ export type MonacoCompletionRequest = (
40
+ context: MonacoCompletionContext,
41
+ ) => MonacoCompletionSnippet[] | Promise<MonacoCompletionSnippet[]>
42
+
43
+ /** 补全前缀解析:由业务侧按方言注入,默认按标识符切词。 */
44
+ export type MonacoCompletionPrefixResolver = (linePrefix: string) => string
45
+
46
+ const props = withDefaults(
47
+ defineProps<{
48
+ language?: MonacoLanguage
49
+ /** px 整数或 CSS 字符串如 '100%' / '20rem' */
50
+ height?: number | string
51
+ readonly?: boolean
52
+ /** 深色/浅色主题:auto 跟随 data-rs-theme 属性 */
53
+ theme?: 'auto' | 'vs-dark' | 'light'
54
+ /** 是否显示 minimap */
55
+ minimap?: boolean
56
+ /**
57
+ * 是否显示 glyph margin(VS Code 式断点槽)。
58
+ * Monaco 无完整 DAP;断点/当前行靠 glyphMargin + decorations(与 VS Code 同源)。
59
+ */
60
+ glyphMargin?: boolean
61
+ /**
62
+ * 调试:当前执行行(1-based)。与 debugBreakpoints 一并交给公共装饰层渲染。
63
+ */
64
+ debugCurrentLine?: number
65
+ /** 调试:断点行号列表(1-based,编辑器行号) */
66
+ debugBreakpoints?: number[]
67
+ /** 嵌入面板:无边框/圆角,铺满父级(调试/源码等) */
68
+ embedded?: boolean
69
+ /**
70
+ * JSON Schema 对象(仅 language="json" 有效)。
71
+ * 每个编辑器实例使用独立的 Model URI,schema 通过 fileMatch 精确绑定,
72
+ * 不会污染其他 JSON 编辑器。
73
+ */
74
+ jsonSchema?: object
75
+ /** 实例级补全片段(language="json" 时 Ctrl+Space / 触发字符生效)。 */
76
+ snippets?: MonacoCompletionSnippet[]
77
+ /** 按光标上下文动态生成候选;设置后与 snippets 合并。 */
78
+ completionRequest?: MonacoCompletionRequest
79
+ /** 自动触发动态补全的字符。 */
80
+ completionTriggerCharacters?: string[]
81
+ /**
82
+ * 自定义补全前缀(决定替换范围)。
83
+ * 默认按标识符字符切词;Mongo Shell / SQL 等方言由业务 composable 注入,勿写进本组件。
84
+ */
85
+ completionPrefixResolver?: MonacoCompletionPrefixResolver
86
+ /**
87
+ * 覆盖/合并 Monaco Editor 构造选项(如 automaticLayout、suggest)。
88
+ * 浅合并顶层;`suggest` 会与默认 suggest 再浅合并一层。
89
+ */
90
+ options?: Record<string, unknown>
91
+ }>(),
92
+ {
93
+ language: 'json',
94
+ height: '100%',
95
+ readonly: false,
96
+ theme: 'auto',
97
+ minimap: false,
98
+ glyphMargin: false,
99
+ debugCurrentLine: 0,
100
+ debugBreakpoints: () => [],
101
+ embedded: false,
102
+ completionTriggerCharacters: () => ['"', '$', '{', ':', ','],
103
+ },
104
+ )
105
+
106
+ const emit = defineEmits<{
107
+ /** glyph 槽点击(仅 glyphMargin=true);业务据此切换断点 */
108
+ glyphMarginClick: [line: number]
109
+ }>()
110
+
111
+ const model = defineModel<string>({ default: '' })
112
+
113
+ const editorEl = ref<HTMLElement | null>(null)
114
+
115
+ let editor: monaco.editor.IStandaloneCodeEditor | null = null
116
+ let editorModel: monaco.editor.ITextModel | null = null
117
+ /** 每个实例的唯一 schema URI,用于 fileMatch 精确匹配,避免全局污染 */
118
+ let schemaUri = ''
119
+ let snippetDisposable: monaco.IDisposable | null = null
120
+ let glyphClickDisposable: monaco.IDisposable | null = null
121
+ let debugDecoIds: string[] = []
122
+ let suppressChange = false
123
+ let unmounted = false
124
+
125
+ function syncDebugDecorations(): void {
126
+ if (!editor || !props.glyphMargin) {
127
+ if (editor && debugDecoIds.length) {
128
+ debugDecoIds = editor.deltaDecorations(debugDecoIds, [])
129
+ }
130
+ return
131
+ }
132
+ debugDecoIds = applyMonacoDebugDecorations(editor, monaco, debugDecoIds, {
133
+ currentLine: props.debugCurrentLine ?? 0,
134
+ breakpointLines: props.debugBreakpoints ?? [],
135
+ })
136
+ }
137
+
138
+ function bindGlyphClick(): void {
139
+ glyphClickDisposable?.dispose()
140
+ glyphClickDisposable = null
141
+ if (!editor || !props.glyphMargin) return
142
+ glyphClickDisposable = editor.onMouseDown((e) => {
143
+ if (e.target.type !== monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN) return
144
+ const line = e.target.position?.lineNumber
145
+ if (!line) return
146
+ emit('glyphMarginClick', line)
147
+ })
148
+ }
149
+
150
+ // ── Theme ──────────────────────────────────────────────────────────────
151
+ function resolveTheme(): string {
152
+ if (props.theme !== 'auto') return props.theme
153
+ return document.documentElement.dataset['rsTheme'] === 'light' ? 'light' : 'vs-dark'
154
+ }
155
+
156
+ function applyTheme(): void {
157
+ monaco.editor.setTheme(resolveTheme())
158
+ }
159
+
160
+ // ── JSON Schema ─────────────────────────────────────────────────────────
161
+ /**
162
+ * 将当前 jsonSchema 应用到 JSON Language Defaults。
163
+ * Monaco v0.55+ 使用顶层 `monaco.json` 命名空间(原 `monaco.languages.json` 已废弃)。
164
+ * 每实例使用独立 schemaUri,通过 fileMatch 精确绑定,不污染其他编辑器。
165
+ */
166
+ function applyJsonSchema(): void {
167
+ if (props.language !== 'json') return
168
+
169
+ // monaco.json 是 v0.55 的顶层 JSON 命名空间
170
+ const defaults = monaco.json.jsonDefaults
171
+ const existing = defaults.diagnosticsOptions
172
+ const schemas = (existing.schemas ?? []).filter(
173
+ (s: { uri: string }) => s.uri !== schemaUri,
174
+ )
175
+
176
+ if (props.jsonSchema && schemaUri) {
177
+ schemas.push({
178
+ uri: schemaUri,
179
+ fileMatch: [schemaUri],
180
+ schema: props.jsonSchema,
181
+ })
182
+ }
183
+
184
+ defaults.setDiagnosticsOptions({ ...existing, validate: true, schemas })
185
+ }
186
+
187
+ function removeJsonSchema(): void {
188
+ if (!schemaUri) return
189
+ const defaults = monaco.json.jsonDefaults
190
+ const existing = defaults.diagnosticsOptions
191
+ const schemas = (existing.schemas ?? []).filter((s: { uri: string }) => s.uri !== schemaUri)
192
+ defaults.setDiagnosticsOptions({ ...existing, schemas })
193
+ }
194
+
195
+ function disposeSnippets(): void {
196
+ snippetDisposable?.dispose()
197
+ snippetDisposable = null
198
+ }
199
+
200
+ function completionKind(kind: MonacoCompletionSnippet['kind']): monaco.languages.CompletionItemKind {
201
+ switch (kind) {
202
+ case 'keyword': return monaco.languages.CompletionItemKind.Keyword
203
+ case 'property': return monaco.languages.CompletionItemKind.Property
204
+ case 'field': return monaco.languages.CompletionItemKind.Field
205
+ case 'function': return monaco.languages.CompletionItemKind.Function
206
+ case 'value': return monaco.languages.CompletionItemKind.Value
207
+ default: return monaco.languages.CompletionItemKind.Snippet
208
+ }
209
+ }
210
+
211
+ function completionPrefix(line: string): string {
212
+ if (props.completionPrefixResolver) {
213
+ return props.completionPrefixResolver(line)
214
+ }
215
+ // 默认:按标识符字符向前切词(与 JSON / 多数脚本方言够用)
216
+ let start = line.length
217
+ while (start > 0 && /[\w$.-]/.test(line[start - 1] ?? '')) start -= 1
218
+ return line.slice(start)
219
+ }
220
+
221
+ function isManagedSqlDialect(language: string | undefined): boolean {
222
+ // mysql / dameng / kingbase:Bridge LSP 接管补全;勿再注册实例级 Provider
223
+ return (
224
+ language === MONACO_MYSQL_LANGUAGE ||
225
+ language === MONACO_DAMENG_LANGUAGE ||
226
+ language === MONACO_KINGBASE_LANGUAGE
227
+ )
228
+ }
229
+
230
+ /** 合并业务侧 options;suggest 做一层浅合并,避免整对象覆盖默认。 */
231
+ function mergeEditorOptions(
232
+ overrides: Record<string, unknown> | undefined,
233
+ ): Record<string, unknown> {
234
+ if (!overrides) return {}
235
+ const out: Record<string, unknown> = { ...overrides }
236
+ if (
237
+ overrides.suggest &&
238
+ typeof overrides.suggest === 'object' &&
239
+ !Array.isArray(overrides.suggest)
240
+ ) {
241
+ out.suggest = {
242
+ snippetsPreventQuickSuggestions: false,
243
+ filterGraceful: !isManagedSqlDialect(props.language),
244
+ matchOnWordStartOnly: isManagedSqlDialect(props.language),
245
+ ...(overrides.suggest as Record<string, unknown>),
246
+ }
247
+ }
248
+ return out
249
+ }
250
+
251
+ function completionLanguages(): string[] {
252
+ if (isManagedSqlDialect(props.language)) {
253
+ return []
254
+ }
255
+ if (
256
+ props.language === 'javascript' ||
257
+ props.language === 'typescript' ||
258
+ props.language === MONACO_MONGODB_SHELL_LANGUAGE ||
259
+ props.language === 'plaintext' ||
260
+ props.language === 'sql' ||
261
+ props.language === 'yaml'
262
+ ) {
263
+ return [props.language]
264
+ }
265
+ if (props.language === 'json') {
266
+ return ['json']
267
+ }
268
+ return []
269
+ }
270
+
271
+ function applySnippets(): void {
272
+ disposeSnippets()
273
+ const languages = completionLanguages()
274
+ if (
275
+ languages.length === 0 ||
276
+ !editorModel ||
277
+ (!props.snippets?.length && !props.completionRequest)
278
+ ) return
279
+
280
+ const modelUri = editorModel.uri.toString()
281
+ for (const language of languages) {
282
+ snippetDisposable = monaco.languages.registerCompletionItemProvider(language, {
283
+ triggerCharacters: props.completionTriggerCharacters,
284
+ async provideCompletionItems(model, position, context, token) {
285
+ if (model.uri.toString() !== modelUri) {
286
+ return { suggestions: [] }
287
+ }
288
+ const linePrefix = model.getLineContent(position.lineNumber).slice(0, position.column - 1)
289
+ const prefix = completionPrefix(linePrefix)
290
+ const startColumn = position.column - prefix.length
291
+ const range = {
292
+ startLineNumber: position.lineNumber,
293
+ endLineNumber: position.lineNumber,
294
+ startColumn,
295
+ endColumn: position.column,
296
+ }
297
+ const dynamic = props.completionRequest
298
+ ? await props.completionRequest({
299
+ text: model.getValue(),
300
+ offset: model.getOffsetAt(position),
301
+ line: position.lineNumber,
302
+ column: position.column,
303
+ prefix,
304
+ triggerCharacter: context.triggerCharacter,
305
+ })
306
+ : []
307
+ if (token.isCancellationRequested) return { suggestions: [] }
308
+ const snippets = [...(props.snippets ?? []), ...dynamic]
309
+ return {
310
+ incomplete: true,
311
+ suggestions: snippets.map((snippet) => ({
312
+ label: snippet.label,
313
+ kind: completionKind(snippet.kind),
314
+ insertText: snippet.insertText,
315
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
316
+ detail: snippet.detail,
317
+ documentation: snippet.documentation,
318
+ filterText: snippet.filterText ?? snippet.label,
319
+ sortText: snippet.sortText,
320
+ preselect: snippet.preselect,
321
+ range,
322
+ })),
323
+ }
324
+ },
325
+ })
326
+ }
327
+ }
328
+
329
+ function disposeEditor(): void {
330
+ removeJsonSchema()
331
+ disposeSnippets()
332
+ glyphClickDisposable?.dispose()
333
+ glyphClickDisposable = null
334
+ debugDecoIds = []
335
+ editor?.dispose()
336
+ editorModel?.dispose()
337
+ editor = null
338
+ editorModel = null
339
+ }
340
+
341
+ // ── Editor ──────────────────────────────────────────────────────────────
342
+ function initEditor(): void {
343
+ if (!editorEl.value || unmounted) return
344
+
345
+ disposeEditor()
346
+
347
+ if (props.language === MONACO_MONGODB_SHELL_LANGUAGE) {
348
+ ensureMongodbShellLanguage(monaco)
349
+ }
350
+
351
+ // 为每个实例生成唯一 URI,使 JSON Schema 精准匹配
352
+ schemaUri = `inmemory://rs-monaco/${crypto.randomUUID()}.json`
353
+
354
+ editorModel = monaco.editor.createModel(
355
+ model.value,
356
+ props.language,
357
+ props.language === 'json' ? monaco.Uri.parse(schemaUri) : undefined,
358
+ )
359
+
360
+ editor = monaco.editor.create(editorEl.value, {
361
+ model: editorModel,
362
+ theme: resolveTheme(),
363
+ readOnly: props.readonly,
364
+ automaticLayout: true,
365
+ minimap: { enabled: props.minimap },
366
+ scrollBeyondLastLine: false,
367
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New', monospace",
368
+ fontSize: 13,
369
+ lineHeight: 20,
370
+ tabSize: 2,
371
+ wordWrap: 'on',
372
+ bracketPairColorization: { enabled: true },
373
+ padding: { top: 8, bottom: 8 },
374
+ renderLineHighlight: 'gutter',
375
+ smoothScrolling: true,
376
+ cursorSmoothCaretAnimation: 'on',
377
+ folding: true,
378
+ glyphMargin: props.glyphMargin,
379
+ // 行号区域仅保留 3 位宽度(≤999 行绰绰有余),减少左侧空白
380
+ lineNumbersMinChars: 3,
381
+ // glyph 断点 + linesDecorations 执行箭头时需留出装饰槽宽度
382
+ lineDecorationsWidth: props.glyphMargin ? 16 : 4,
383
+ overviewRulerLanes: 0,
384
+ hideCursorInOverviewRuler: true,
385
+ // Shell:业务 completionRequest;mysql/dameng/kingbase:Bridge LSP;避免 wordBased 抢补全
386
+ quickSuggestions: props.language === MONACO_MONGODB_SHELL_LANGUAGE
387
+ ? { other: true, comments: false, strings: true }
388
+ : true,
389
+ suggestOnTriggerCharacters: true,
390
+ wordBasedSuggestions:
391
+ props.language === MONACO_MONGODB_SHELL_LANGUAGE ||
392
+ isManagedSqlDialect(props.language)
393
+ ? 'off'
394
+ : 'currentDocument',
395
+ suggest: {
396
+ snippetsPreventQuickSuggestions: false,
397
+ // catalog 已按前缀检索;关闭宽松模糊,避免短前缀下列表「看起来像乱匹配」
398
+ filterGraceful: !isManagedSqlDialect(props.language),
399
+ matchOnWordStartOnly: isManagedSqlDialect(props.language),
400
+ },
401
+ scrollbar: {
402
+ verticalScrollbarSize: 8,
403
+ horizontalScrollbarSize: 8,
404
+ },
405
+ // Find Widget 等溢出控件使用 fixed 定位,避免被 overflow:hidden 裁剪
406
+ fixedOverflowWidgets: true,
407
+ ...mergeEditorOptions(props.options),
408
+ })
409
+
410
+ editorModel.onDidChangeContent(() => {
411
+ if (suppressChange) return
412
+ const value = editorModel?.getValue() ?? ''
413
+ if (value !== model.value) model.value = value
414
+ })
415
+
416
+ applyJsonSchema()
417
+ applySnippets()
418
+ bindGlyphClick()
419
+ syncDebugDecorations()
420
+ }
421
+
422
+ // ── Lifecycle ──────────────────────────────────────────────────────────
423
+ let themeObserver: MutationObserver | null = null
424
+
425
+ onMounted(() => {
426
+ unmounted = false
427
+ initEditor()
428
+ themeObserver = new MutationObserver(applyTheme)
429
+ themeObserver.observe(document.documentElement, {
430
+ attributes: true,
431
+ attributeFilter: ['data-rs-theme'],
432
+ })
433
+ })
434
+
435
+ onUnmounted(() => {
436
+ unmounted = true
437
+ glyphClickDisposable?.dispose()
438
+ glyphClickDisposable = null
439
+ debugDecoIds = []
440
+ disposeEditor()
441
+ themeObserver?.disconnect()
442
+ themeObserver = null
443
+ })
444
+
445
+ // ── Watchers ──────────────────────────────────────────────────────────
446
+ watch(
447
+ () => model.value,
448
+ (val) => {
449
+ if (!editorModel) return
450
+ if (editorModel.getValue() === val) return
451
+ suppressChange = true
452
+ editorModel.setValue(val)
453
+ suppressChange = false
454
+ },
455
+ )
456
+
457
+ watch(() => props.readonly, (val) => editor?.updateOptions({ readOnly: val }))
458
+ watch(() => props.minimap, (val) => editor?.updateOptions({ minimap: { enabled: val } }))
459
+ watch(
460
+ () => props.glyphMargin,
461
+ (val) => {
462
+ editor?.updateOptions({
463
+ glyphMargin: val,
464
+ lineDecorationsWidth: val ? 16 : 4,
465
+ })
466
+ bindGlyphClick()
467
+ syncDebugDecorations()
468
+ },
469
+ )
470
+ watch(
471
+ () => [props.debugCurrentLine, props.debugBreakpoints] as const,
472
+ () => {
473
+ syncDebugDecorations()
474
+ },
475
+ { deep: true },
476
+ )
477
+ watch(() => props.language, () => initEditor())
478
+ watch(() => props.jsonSchema, () => applyJsonSchema(), { deep: true })
479
+ watch(() => props.snippets, () => applySnippets(), { deep: true })
480
+ watch(() => props.completionRequest, () => applySnippets())
481
+ watch(() => props.completionTriggerCharacters, () => applySnippets(), { deep: true })
482
+ watch(() => props.completionPrefixResolver, () => applySnippets())
483
+
484
+ // ── Expose ────────────────────────────────────────────────────────────
485
+ defineExpose({
486
+ /** 格式化文档(等价于 Shift+Alt+F) */
487
+ format(): void {
488
+ editor?.getAction('editor.action.formatDocument')?.run().catch(() => undefined)
489
+ },
490
+ getEditor(): monaco.editor.IStandaloneCodeEditor | null {
491
+ return editor
492
+ },
493
+ /** 将指定行滚入视口中央(调试当前行) */
494
+ revealLine(line: number): void {
495
+ if (!editor || line <= 0) return
496
+ editor.revealLineInCenter(line)
497
+ },
498
+ })
499
+ </script>
500
+
501
+ <template>
502
+ <div
503
+ ref="editorEl"
504
+ class="rs-monaco"
505
+ :class="{ 'rs-monaco--embedded': embedded }"
506
+ :style="{ height: typeof height === 'number' ? `${height}px` : height }"
507
+ />
508
+ </template>
509
+
510
+ <style scoped>
511
+ .rs-monaco {
512
+ /* hover tooltip 的定位上下文;勿加 overflow:hidden,否则会裁剪 Find Widget 的 tooltip */
513
+ position: relative;
514
+ width: 100%;
515
+ border: 1px solid var(--rs-border);
516
+ border-radius: var(--rs-radius);
517
+ background: var(--rs-surface-elevated);
518
+ }
519
+
520
+ .rs-monaco--embedded {
521
+ border: 0;
522
+ border-radius: 0;
523
+ flex: 1 1 auto;
524
+ min-height: 0;
525
+ height: 100%;
526
+ }
527
+
528
+ :deep(.monaco-editor) {
529
+ border-radius: inherit;
530
+ }
531
+
532
+ :deep(.monaco-editor .overflow-guard) {
533
+ border-radius: inherit;
534
+ overflow: hidden;
535
+ }
536
+
537
+ /* Find Widget z-index:35;Monaco hover 内联 z-index≈2575,层级本身足够 */
538
+ :deep(.monaco-editor .find-widget) {
539
+ z-index: 40;
540
+ }
541
+ </style>