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.
- package/CHANGELOG.md +29 -0
- package/LICENSE +202 -0
- package/NOTICE +25 -0
- package/README.en.md +99 -0
- package/README.md +161 -0
- package/package.json +106 -0
- package/src/__tests__/RsBadge.spec.ts +40 -0
- package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
- package/src/__tests__/RsButton.spec.ts +108 -0
- package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
- package/src/__tests__/RsCard.spec.ts +97 -0
- package/src/__tests__/RsCheckbox.spec.ts +36 -0
- package/src/__tests__/RsCodeEditor.spec.ts +111 -0
- package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
- package/src/__tests__/RsContainer.spec.ts +89 -0
- package/src/__tests__/RsContextMenu.spec.ts +118 -0
- package/src/__tests__/RsDatePicker.spec.ts +88 -0
- package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
- package/src/__tests__/RsDialog.spec.ts +404 -0
- package/src/__tests__/RsDrawer.spec.ts +145 -0
- package/src/__tests__/RsDropdown.spec.ts +135 -0
- package/src/__tests__/RsEmpty.spec.ts +99 -0
- package/src/__tests__/RsForm.spec.ts +173 -0
- package/src/__tests__/RsIcon.spec.ts +244 -0
- package/src/__tests__/RsInput.spec.ts +269 -0
- package/src/__tests__/RsInputNumber.spec.ts +118 -0
- package/src/__tests__/RsLabel.spec.ts +106 -0
- package/src/__tests__/RsLink.spec.ts +49 -0
- package/src/__tests__/RsLoading.spec.ts +84 -0
- package/src/__tests__/RsMarkdown.spec.ts +143 -0
- package/src/__tests__/RsMenu.spec.ts +119 -0
- package/src/__tests__/RsNewComponents.spec.ts +17 -0
- package/src/__tests__/RsPagination.spec.ts +158 -0
- package/src/__tests__/RsPopover.spec.ts +126 -0
- package/src/__tests__/RsScrollbar.spec.ts +53 -0
- package/src/__tests__/RsSelect.spec.ts +291 -0
- package/src/__tests__/RsSidebar.spec.ts +119 -0
- package/src/__tests__/RsSplitPane.spec.ts +177 -0
- package/src/__tests__/RsStatCard.spec.ts +79 -0
- package/src/__tests__/RsSteps.spec.ts +125 -0
- package/src/__tests__/RsTable.perf.spec.ts +103 -0
- package/src/__tests__/RsTable.spec.ts +1135 -0
- package/src/__tests__/RsTabs.spec.ts +328 -0
- package/src/__tests__/RsTerminal.spec.ts +218 -0
- package/src/__tests__/RsTimePicker.spec.ts +77 -0
- package/src/__tests__/RsToaster.spec.ts +95 -0
- package/src/__tests__/RsToolbar.spec.ts +75 -0
- package/src/__tests__/RsTooltip.spec.ts +111 -0
- package/src/__tests__/RsTree.spec.ts +572 -0
- package/src/__tests__/RsUpload.spec.ts +160 -0
- package/src/__tests__/RsVirtualList.spec.ts +77 -0
- package/src/__tests__/date-picker-utils.spec.ts +70 -0
- package/src/__tests__/dialog-viewport.spec.ts +38 -0
- package/src/__tests__/iso-local-datetime.spec.ts +58 -0
- package/src/__tests__/rs-clipboard.spec.ts +142 -0
- package/src/__tests__/setup.ts +12 -0
- package/src/__tests__/split-pane-utils.spec.ts +136 -0
- package/src/__tests__/table-drag.spec.ts +35 -0
- package/src/__tests__/table-edit-utils.spec.ts +194 -0
- package/src/__tests__/table-utils.spec.ts +286 -0
- package/src/__tests__/theme.spec.ts +22 -0
- package/src/__tests__/tree-utils.spec.ts +234 -0
- package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
- package/src/components/RsAvatar.vue +115 -0
- package/src/components/RsBadge.vue +59 -0
- package/src/components/RsBreadcrumb.vue +65 -0
- package/src/components/RsBreadcrumbItems.vue +36 -0
- package/src/components/RsButton.vue +393 -0
- package/src/components/RsCalendarGrid.vue +284 -0
- package/src/components/RsCard.vue +237 -0
- package/src/components/RsCheckbox.vue +189 -0
- package/src/components/RsCodeBlock.vue +278 -0
- package/src/components/RsCodeEditor.vue +597 -0
- package/src/components/RsConfigProvider.vue +92 -0
- package/src/components/RsConfirmDialog.vue +178 -0
- package/src/components/RsContainer.vue +235 -0
- package/src/components/RsContextMenu.vue +238 -0
- package/src/components/RsContextMenuItems.vue +77 -0
- package/src/components/RsDatePicker.vue +756 -0
- package/src/components/RsDateTimePicker.vue +42 -0
- package/src/components/RsDialog.vue +486 -0
- package/src/components/RsDrawer.vue +175 -0
- package/src/components/RsDropdown.vue +252 -0
- package/src/components/RsDropdownItems.vue +90 -0
- package/src/components/RsEmpty.vue +107 -0
- package/src/components/RsForm.vue +131 -0
- package/src/components/RsIcon.vue +104 -0
- package/src/components/RsInput.vue +1101 -0
- package/src/components/RsInputNumber.vue +532 -0
- package/src/components/RsLabel.vue +54 -0
- package/src/components/RsLink.vue +106 -0
- package/src/components/RsLoading.vue +171 -0
- package/src/components/RsMarkdown.vue +329 -0
- package/src/components/RsMenu.vue +256 -0
- package/src/components/RsMenuItems.vue +125 -0
- package/src/components/RsMonacoEditor.vue +541 -0
- package/src/components/RsPagination.vue +184 -0
- package/src/components/RsPopover.vue +77 -0
- package/src/components/RsProseEditor.vue +160 -0
- package/src/components/RsScrollbar.vue +171 -0
- package/src/components/RsSelect.vue +1045 -0
- package/src/components/RsSidebar.vue +121 -0
- package/src/components/RsSidebarGroup.vue +32 -0
- package/src/components/RsSidebarItem.vue +72 -0
- package/src/components/RsSplitPane.vue +619 -0
- package/src/components/RsStatCard.vue +85 -0
- package/src/components/RsSteps.vue +138 -0
- package/src/components/RsTable.vue +2664 -0
- package/src/components/RsTableColgroup.vue +17 -0
- package/src/components/RsTableHeaderRow.vue +30 -0
- package/src/components/RsTabs.vue +951 -0
- package/src/components/RsTerminal.vue +697 -0
- package/src/components/RsTimePicker.vue +554 -0
- package/src/components/RsTimePickerColumns.vue +226 -0
- package/src/components/RsToaster.vue +396 -0
- package/src/components/RsToolbar.vue +135 -0
- package/src/components/RsTooltip.vue +142 -0
- package/src/components/RsTooltipProvider.vue +20 -0
- package/src/components/RsTree.vue +1248 -0
- package/src/components/RsUpload.vue +279 -0
- package/src/components/RsVirtualList.vue +217 -0
- package/src/components/avatar-utils.ts +19 -0
- package/src/components/breadcrumb-utils.ts +19 -0
- package/src/components/class-utils.ts +29 -0
- package/src/components/code-editor-utils.ts +106 -0
- package/src/components/code-mirror-completion.ts +40 -0
- package/src/components/code-mirror-diagnostics.ts +52 -0
- package/src/components/code-mirror-ghost.ts +158 -0
- package/src/components/code-mirror-inline-edit.ts +55 -0
- package/src/components/code-mirror-intel.ts +81 -0
- package/src/components/code-mirror-lang.ts +128 -0
- package/src/components/context-menu-utils.ts +20 -0
- package/src/components/date-picker-utils.ts +284 -0
- package/src/components/date-range-validation.ts +29 -0
- package/src/components/date-validation.ts +38 -0
- package/src/components/dialog-viewport.ts +48 -0
- package/src/components/dialog-window.ts +253 -0
- package/src/components/dropdown-utils.ts +31 -0
- package/src/components/form-utils.ts +77 -0
- package/src/components/input-number-utils.ts +128 -0
- package/src/components/input-rules.ts +84 -0
- package/src/components/loading-utils.ts +8 -0
- package/src/components/markdown-utils.ts +126 -0
- package/src/components/menu-utils.ts +48 -0
- package/src/components/overlay-utils.ts +47 -0
- package/src/components/pagination-utils.ts +38 -0
- package/src/components/popover-utils.ts +8 -0
- package/src/components/reka.ts +106 -0
- package/src/components/resolve-radius.ts +22 -0
- package/src/components/resolve-size.ts +29 -0
- package/src/components/scrollbar-utils.ts +10 -0
- package/src/components/select-utils.ts +72 -0
- package/src/components/split-pane-utils.ts +187 -0
- package/src/components/steps-utils.ts +22 -0
- package/src/components/table/RsTableBodyRow.vue +287 -0
- package/src/components/table/RsTableCell.vue +239 -0
- package/src/components/table/RsTableCellContent.ts +18 -0
- package/src/components/table/RsTableCellEditor.vue +351 -0
- package/src/components/table/RsTableEditGutter.vue +54 -0
- package/src/components/table/RsTableHeaderFilter.vue +107 -0
- package/src/components/table/RsTableRowStatus.vue +69 -0
- package/src/components/table/rs-table.css +1141 -0
- package/src/components/table/table-context-menu.ts +53 -0
- package/src/components/table/table-edit-utils.ts +496 -0
- package/src/components/table/table-keys.ts +12 -0
- package/src/components/table-drag.ts +215 -0
- package/src/components/table-utils.ts +950 -0
- package/src/components/tabs-utils.ts +107 -0
- package/src/components/terminal-utils.ts +301 -0
- package/src/components/terminal-wheel.ts +49 -0
- package/src/components/time-picker-utils.ts +167 -0
- package/src/components/time-range-validation.ts +23 -0
- package/src/components/time-validation.ts +20 -0
- package/src/components/tree-utils.ts +518 -0
- package/src/components/upload-utils.ts +55 -0
- package/src/components/virtual-list-utils.ts +17 -0
- package/src/composables/useClickOutside.ts +22 -0
- package/src/composables/useRsConfig.ts +91 -0
- package/src/composables/useRsI18n.ts +22 -0
- package/src/composables/useRsTabsNav.ts +187 -0
- package/src/composables/useRsToast.ts +48 -0
- package/src/composables/useTableCellTooltip.ts +239 -0
- package/src/composables/useTableEdit.ts +271 -0
- package/src/composables/useTableRowHighlight.ts +50 -0
- package/src/dev/vite-codemirror-deps.ts +20 -0
- package/src/icons/custom/api.ts +54 -0
- package/src/icons/custom/clickhouse.ts +52 -0
- package/src/icons/custom/dameng.ts +83 -0
- package/src/icons/custom/ftp.ts +61 -0
- package/src/icons/custom/kingbase.ts +52 -0
- package/src/icons/custom/mongodb.ts +50 -0
- package/src/icons/custom/mysql.ts +51 -0
- package/src/icons/custom/oracle.ts +52 -0
- package/src/icons/custom/redis.ts +51 -0
- package/src/icons/custom/sqlite.ts +50 -0
- package/src/icons/custom/sqlserver.ts +52 -0
- package/src/icons/custom/vastbase.ts +57 -0
- package/src/icons/lucide.ts +78 -0
- package/src/icons/registry.ts +35 -0
- package/src/index.ts +425 -0
- package/src/lib/iso-local-datetime.ts +152 -0
- package/src/lib/rs-dayjs.ts +56 -0
- package/src/locale/messages.ts +362 -0
- package/src/locale/types.ts +5 -0
- package/src/monaco/debug-decorations.css +20 -0
- package/src/monaco/debug-decorations.ts +70 -0
- package/src/monaco/index.ts +22 -0
- package/src/monaco/languages/index.ts +21 -0
- package/src/monaco/languages/mongodb-shell.ts +125 -0
- package/src/monaco/languages/types.ts +55 -0
- package/src/monaco/setup.ts +101 -0
- package/src/styles.css +1040 -0
- package/src/theme/apply.ts +12 -0
- package/src/theme/brand.example.css +37 -0
- package/src/theme/presets.ts +69 -0
- package/src/theme/types.ts +81 -0
- package/src/utils/rs-clipboard.ts +272 -0
- package/src/vite-env.d.ts +7 -0
- package/vite-plugins/monaco-zh-nls.ts +101 -0
- package/vite-plugins/silence-antlr-parse-console.ts +29 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Compartment, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state'
|
|
2
|
+
import { Decoration, EditorView } from '@codemirror/view'
|
|
3
|
+
import type { RsCodeEditorDiagnostic } from './code-editor-utils'
|
|
4
|
+
|
|
5
|
+
const setDiagnosticsEffect = StateEffect.define<RsCodeEditorDiagnostic[]>()
|
|
6
|
+
|
|
7
|
+
function buildLineDecorations(doc: { lines: number; line: (n: number) => { from: number } }, diags: RsCodeEditorDiagnostic[]) {
|
|
8
|
+
const builder = new RangeSetBuilder<Decoration>()
|
|
9
|
+
for (const d of diags) {
|
|
10
|
+
const lineNo = d.line ?? 1
|
|
11
|
+
if (lineNo < 1 || lineNo > doc.lines) continue
|
|
12
|
+
const line = doc.line(lineNo)
|
|
13
|
+
const cls =
|
|
14
|
+
d.severity === 'warning'
|
|
15
|
+
? 'cm-lintRange cm-lintRange-warning'
|
|
16
|
+
: d.severity === 'info'
|
|
17
|
+
? 'cm-lintRange cm-lintRange-info'
|
|
18
|
+
: 'cm-lintRange cm-lintRange-error'
|
|
19
|
+
builder.add(line.from, line.from, Decoration.line({ class: cls }))
|
|
20
|
+
}
|
|
21
|
+
return builder.finish()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const diagnosticField = StateField.define({
|
|
25
|
+
create() {
|
|
26
|
+
return Decoration.none
|
|
27
|
+
},
|
|
28
|
+
update(deco, tr) {
|
|
29
|
+
for (const e of tr.effects) {
|
|
30
|
+
if (e.is(setDiagnosticsEffect)) {
|
|
31
|
+
return buildLineDecorations(tr.state.doc, e.value)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (tr.docChanged) {
|
|
35
|
+
return deco.map(tr.changes)
|
|
36
|
+
}
|
|
37
|
+
return deco
|
|
38
|
+
},
|
|
39
|
+
provide: (f) => EditorView.decorations.from(f),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export const diagnosticCompartment = new Compartment()
|
|
43
|
+
|
|
44
|
+
export function diagnosticExtensions() {
|
|
45
|
+
return diagnosticCompartment.of([diagnosticField])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function setEditorDiagnostics(view: EditorView, diags: RsCodeEditorDiagnostic[]) {
|
|
49
|
+
view.dispatch({
|
|
50
|
+
effects: setDiagnosticsEffect.of(diags),
|
|
51
|
+
})
|
|
52
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Prec, StateEffect, StateField, type Extension } from '@codemirror/state'
|
|
2
|
+
import { Decoration, EditorView, keymap, WidgetType } from '@codemirror/view'
|
|
3
|
+
import { posToLineColumn } from './code-mirror-intel'
|
|
4
|
+
|
|
5
|
+
export const setGhostCompletion = StateEffect.define<string | null>()
|
|
6
|
+
|
|
7
|
+
interface GhostMeta {
|
|
8
|
+
text: string
|
|
9
|
+
pos: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class GhostWidget extends WidgetType {
|
|
13
|
+
constructor(readonly text: string) {
|
|
14
|
+
super()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
eq(other: GhostWidget) {
|
|
18
|
+
return other.text === this.text
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
toDOM() {
|
|
22
|
+
const span = document.createElement('span')
|
|
23
|
+
span.className = 'rs-code-editor__ghost'
|
|
24
|
+
span.textContent = this.text
|
|
25
|
+
return span
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ignoreEvent() {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ghostMetaField = StateField.define<GhostMeta | null>({
|
|
34
|
+
create() {
|
|
35
|
+
return null
|
|
36
|
+
},
|
|
37
|
+
update(value, tr) {
|
|
38
|
+
for (const effect of tr.effects) {
|
|
39
|
+
if (effect.is(setGhostCompletion)) {
|
|
40
|
+
if (!effect.value) return null
|
|
41
|
+
return { text: effect.value, pos: tr.state.selection.main.head }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (tr.docChanged) return null
|
|
45
|
+
if (tr.selection && !tr.effects.some((e) => e.is(setGhostCompletion))) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
return value
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const ghostField = StateField.define<ReturnType<typeof Decoration.set>>({
|
|
53
|
+
create() {
|
|
54
|
+
return Decoration.none
|
|
55
|
+
},
|
|
56
|
+
update(deco, tr) {
|
|
57
|
+
deco = deco.map(tr.changes)
|
|
58
|
+
for (const effect of tr.effects) {
|
|
59
|
+
if (effect.is(setGhostCompletion)) {
|
|
60
|
+
if (!effect.value) return Decoration.none
|
|
61
|
+
const pos = tr.state.selection.main.head
|
|
62
|
+
return Decoration.set([
|
|
63
|
+
Decoration.widget({
|
|
64
|
+
widget: new GhostWidget(effect.value),
|
|
65
|
+
side: 1,
|
|
66
|
+
}).range(pos),
|
|
67
|
+
])
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (tr.docChanged || tr.selection) {
|
|
71
|
+
const hasSet = tr.effects.some((e) => e.is(setGhostCompletion))
|
|
72
|
+
if (!hasSet) return Decoration.none
|
|
73
|
+
}
|
|
74
|
+
return deco
|
|
75
|
+
},
|
|
76
|
+
provide: (field) => EditorView.decorations.from(field),
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
export interface CodeMirrorGhostHandlers {
|
|
80
|
+
completionRequest?: (prefix: string, suffix: string, line: number, column: number) => Promise<string | null>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readGhostAtCursor(view: EditorView): GhostMeta | null {
|
|
84
|
+
const meta = view.state.field(ghostMetaField, false)
|
|
85
|
+
if (!meta) return null
|
|
86
|
+
if (meta.pos !== view.state.selection.main.head) return null
|
|
87
|
+
return meta
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function codeMirrorGhostExtensions(
|
|
91
|
+
handlers: () => CodeMirrorGhostHandlers,
|
|
92
|
+
scheduleDebounced: (fn: () => void) => void,
|
|
93
|
+
cancelScheduled?: () => void,
|
|
94
|
+
): Extension[] {
|
|
95
|
+
let seq = 0
|
|
96
|
+
return [
|
|
97
|
+
ghostMetaField,
|
|
98
|
+
ghostField,
|
|
99
|
+
EditorView.updateListener.of((update) => {
|
|
100
|
+
if (!update.docChanged) return
|
|
101
|
+
const h = handlers()
|
|
102
|
+
if (!h.completionRequest) return
|
|
103
|
+
cancelScheduled?.()
|
|
104
|
+
const id = ++seq
|
|
105
|
+
scheduleDebounced(() => {
|
|
106
|
+
const view = update.view
|
|
107
|
+
const pos = view.state.selection.main.head
|
|
108
|
+
const prefix = view.state.doc.sliceString(0, pos)
|
|
109
|
+
const suffix = view.state.doc.sliceString(pos)
|
|
110
|
+
const { line, column } = posToLineColumn(view, pos)
|
|
111
|
+
void h.completionRequest!(prefix, suffix, line, column).then((text) => {
|
|
112
|
+
if (id !== seq) return
|
|
113
|
+
if (!text?.trim()) {
|
|
114
|
+
view.dispatch({ effects: setGhostCompletion.of(null) })
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
const current = view.state.selection.main.head
|
|
118
|
+
if (view.state.doc.sliceString(current) !== suffix) return
|
|
119
|
+
view.dispatch({ effects: setGhostCompletion.of(text) })
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
}),
|
|
123
|
+
Prec.highest(
|
|
124
|
+
keymap.of([
|
|
125
|
+
{
|
|
126
|
+
key: 'Tab',
|
|
127
|
+
run: (view) => {
|
|
128
|
+
const ghost = readGhostAtCursor(view)
|
|
129
|
+
if (!ghost) return false
|
|
130
|
+
cancelScheduled?.()
|
|
131
|
+
seq += 1
|
|
132
|
+
const pos = view.state.selection.main.head
|
|
133
|
+
view.dispatch({
|
|
134
|
+
changes: { from: pos, to: pos, insert: ghost.text },
|
|
135
|
+
selection: { anchor: pos + ghost.text.length },
|
|
136
|
+
effects: setGhostCompletion.of(null),
|
|
137
|
+
})
|
|
138
|
+
return true
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: 'Escape',
|
|
143
|
+
run: (view) => {
|
|
144
|
+
if (!readGhostAtCursor(view)) return false
|
|
145
|
+
cancelScheduled?.()
|
|
146
|
+
seq += 1
|
|
147
|
+
view.dispatch({ effects: setGhostCompletion.of(null) })
|
|
148
|
+
return true
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
]),
|
|
152
|
+
),
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function clearGhostCompletion(view: EditorView) {
|
|
157
|
+
view.dispatch({ effects: setGhostCompletion.of(null) })
|
|
158
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Prec, type Extension } from '@codemirror/state'
|
|
2
|
+
import { EditorView, keymap } from '@codemirror/view'
|
|
3
|
+
|
|
4
|
+
export interface InlineEditTrigger {
|
|
5
|
+
selection: string
|
|
6
|
+
from: number
|
|
7
|
+
to: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** 提取选区上下各若干行,供 Inline Edit 理解作用域(不含重复包裹选区)。 */
|
|
11
|
+
export function extractSurroundingContext(doc: string, from: number, to: number, lineRadius = 20): string {
|
|
12
|
+
if (!doc) return ''
|
|
13
|
+
const lines = doc.split('\n')
|
|
14
|
+
let pos = 0
|
|
15
|
+
let startLine = 0
|
|
16
|
+
let endLine = lines.length - 1
|
|
17
|
+
for (let i = 0; i < lines.length; i++) {
|
|
18
|
+
const lineStart = pos
|
|
19
|
+
const lineEnd = pos + lines[i].length
|
|
20
|
+
if (from >= lineStart && from <= lineEnd + (i < lines.length - 1 ? 1 : 0)) startLine = i
|
|
21
|
+
if (to >= lineStart && to <= lineEnd + (i < lines.length - 1 ? 1 : 0)) endLine = i
|
|
22
|
+
pos = lineEnd + 1
|
|
23
|
+
}
|
|
24
|
+
const contextStart = Math.max(0, startLine - lineRadius)
|
|
25
|
+
const contextEnd = Math.min(lines.length - 1, endLine + lineRadius)
|
|
26
|
+
return lines.slice(contextStart, contextEnd + 1).join('\n')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function codeMirrorInlineEditExtension(
|
|
30
|
+
onTrigger: (view: EditorView, info: InlineEditTrigger) => void,
|
|
31
|
+
): Extension {
|
|
32
|
+
return Prec.highest(
|
|
33
|
+
keymap.of([
|
|
34
|
+
{
|
|
35
|
+
key: 'Mod-k',
|
|
36
|
+
run: (view) => {
|
|
37
|
+
const { from, to } = view.state.selection.main
|
|
38
|
+
if (from === to) return false
|
|
39
|
+
const selection = view.state.sliceDoc(from, to)
|
|
40
|
+
if (!selection.trim()) return false
|
|
41
|
+
onTrigger(view, { selection, from, to })
|
|
42
|
+
return true
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
]),
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function replaceEditorRange(view: EditorView, from: number, to: number, insert: string) {
|
|
50
|
+
view.dispatch({
|
|
51
|
+
changes: { from, to, insert },
|
|
52
|
+
selection: { anchor: from + insert.length },
|
|
53
|
+
})
|
|
54
|
+
view.focus()
|
|
55
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { EditorView, hoverTooltip } from '@codemirror/view'
|
|
2
|
+
import type { Extension } from '@codemirror/state'
|
|
3
|
+
|
|
4
|
+
export interface CodeMirrorIntelHandlers {
|
|
5
|
+
hoverRequest?: (line: number, column: number) => Promise<string | null>
|
|
6
|
+
definitionRequest?: (line: number, column: number) => Promise<{
|
|
7
|
+
file: string
|
|
8
|
+
line: number
|
|
9
|
+
column?: number
|
|
10
|
+
} | null>
|
|
11
|
+
currentFilePath?: string
|
|
12
|
+
onGotoDefinition?: (loc: { file: string; line: number; column?: number }) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function posToLineColumn(view: EditorView, pos: number) {
|
|
16
|
+
const line = view.state.doc.lineAt(pos)
|
|
17
|
+
return {
|
|
18
|
+
line: line.number,
|
|
19
|
+
column: pos - line.from + 1,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function goToPosition(view: EditorView, line: number, column = 1) {
|
|
24
|
+
const doc = view.state.doc
|
|
25
|
+
const ln = Math.min(Math.max(1, line), doc.lines)
|
|
26
|
+
const lineObj = doc.line(ln)
|
|
27
|
+
const pos = lineObj.from + Math.max(0, column - 1)
|
|
28
|
+
view.dispatch({
|
|
29
|
+
selection: { anchor: pos },
|
|
30
|
+
scrollIntoView: true,
|
|
31
|
+
})
|
|
32
|
+
view.focus()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function codeMirrorIntelExtensions(handlers: () => CodeMirrorIntelHandlers): Extension[] {
|
|
36
|
+
const hoverExt = hoverTooltip(
|
|
37
|
+
async (view, pos) => {
|
|
38
|
+
const h = handlers()
|
|
39
|
+
if (!h.hoverRequest) return null
|
|
40
|
+
const { line, column } = posToLineColumn(view, pos)
|
|
41
|
+
const text = await h.hoverRequest(line, column)
|
|
42
|
+
if (!text?.trim()) return null
|
|
43
|
+
return {
|
|
44
|
+
pos,
|
|
45
|
+
above: true,
|
|
46
|
+
create() {
|
|
47
|
+
const dom = document.createElement('div')
|
|
48
|
+
dom.className = 'rs-code-editor__hover'
|
|
49
|
+
dom.textContent = text
|
|
50
|
+
return { dom }
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{ hoverTime: 450 },
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const clickExt = EditorView.domEventHandlers({
|
|
58
|
+
mousedown(event, view) {
|
|
59
|
+
if (!event.ctrlKey && !event.metaKey) return false
|
|
60
|
+
const h = handlers()
|
|
61
|
+
if (!h.definitionRequest) return false
|
|
62
|
+
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY })
|
|
63
|
+
if (pos == null) return false
|
|
64
|
+
const { line, column } = posToLineColumn(view, pos)
|
|
65
|
+
void h.definitionRequest(line, column).then((loc) => {
|
|
66
|
+
if (!loc?.line) return
|
|
67
|
+
const current = (h.currentFilePath ?? '').replace(/\\/g, '/')
|
|
68
|
+
const target = (loc.file ?? '').replace(/\\/g, '/')
|
|
69
|
+
if (target && current && target !== current) {
|
|
70
|
+
h.onGotoDefinition?.(loc)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
goToPosition(view, loc.line, loc.column ?? 1)
|
|
74
|
+
})
|
|
75
|
+
event.preventDefault()
|
|
76
|
+
return true
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return [hoverExt, clickExt]
|
|
81
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Extension } from '@codemirror/state'
|
|
2
|
+
import type { RsCodeEditorSqlConfig } from './code-editor-utils'
|
|
3
|
+
|
|
4
|
+
export interface ResolveCodeMirrorLanguageOptions {
|
|
5
|
+
sql?: RsCodeEditorSqlConfig
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** 按语言标识动态加载 CodeMirror 语言扩展 */
|
|
9
|
+
export async function resolveCodeMirrorLanguage(
|
|
10
|
+
lang: string,
|
|
11
|
+
options?: ResolveCodeMirrorLanguageOptions,
|
|
12
|
+
): Promise<Extension[]> {
|
|
13
|
+
const l = lang.toLowerCase().trim()
|
|
14
|
+
try {
|
|
15
|
+
switch (l) {
|
|
16
|
+
case 'js':
|
|
17
|
+
case 'javascript':
|
|
18
|
+
case 'jsx': {
|
|
19
|
+
const { javascript } = await import('@codemirror/lang-javascript')
|
|
20
|
+
return [javascript()]
|
|
21
|
+
}
|
|
22
|
+
case 'ts':
|
|
23
|
+
case 'typescript':
|
|
24
|
+
case 'tsx': {
|
|
25
|
+
const { javascript } = await import('@codemirror/lang-javascript')
|
|
26
|
+
return [javascript({ typescript: true })]
|
|
27
|
+
}
|
|
28
|
+
case 'py':
|
|
29
|
+
case 'python': {
|
|
30
|
+
const { python } = await import('@codemirror/lang-python')
|
|
31
|
+
return [python()]
|
|
32
|
+
}
|
|
33
|
+
case 'go': {
|
|
34
|
+
const { go } = await import('@codemirror/lang-go')
|
|
35
|
+
return [go()]
|
|
36
|
+
}
|
|
37
|
+
case 'html':
|
|
38
|
+
case 'htm': {
|
|
39
|
+
const { html } = await import('@codemirror/lang-html')
|
|
40
|
+
return [html()]
|
|
41
|
+
}
|
|
42
|
+
case 'css':
|
|
43
|
+
case 'scss':
|
|
44
|
+
case 'less': {
|
|
45
|
+
const { css } = await import('@codemirror/lang-css')
|
|
46
|
+
return [css()]
|
|
47
|
+
}
|
|
48
|
+
case 'json':
|
|
49
|
+
case 'jsonc': {
|
|
50
|
+
const { json } = await import('@codemirror/lang-json')
|
|
51
|
+
return [json()]
|
|
52
|
+
}
|
|
53
|
+
case 'sql': {
|
|
54
|
+
const { sql, PostgreSQL, StandardSQL, MySQL } = await import('@codemirror/lang-sql')
|
|
55
|
+
const dialectName = options?.sql?.dialect ?? 'postgresql'
|
|
56
|
+
let dialect = PostgreSQL
|
|
57
|
+
if (dialectName === 'mysql') dialect = MySQL
|
|
58
|
+
else if (dialectName === 'standard') dialect = StandardSQL
|
|
59
|
+
return [
|
|
60
|
+
sql({
|
|
61
|
+
dialect,
|
|
62
|
+
schema: options?.sql?.schema as never,
|
|
63
|
+
defaultTable: options?.sql?.defaultTable,
|
|
64
|
+
defaultSchema: options?.sql?.defaultSchema,
|
|
65
|
+
}),
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
case 'rust':
|
|
69
|
+
case 'rs': {
|
|
70
|
+
const { rust } = await import('@codemirror/lang-rust')
|
|
71
|
+
return [rust()]
|
|
72
|
+
}
|
|
73
|
+
case 'cpp':
|
|
74
|
+
case 'cc':
|
|
75
|
+
case 'cxx':
|
|
76
|
+
case 'c': {
|
|
77
|
+
const { cpp } = await import('@codemirror/lang-cpp')
|
|
78
|
+
return [cpp()]
|
|
79
|
+
}
|
|
80
|
+
case 'java': {
|
|
81
|
+
const { java } = await import('@codemirror/lang-java')
|
|
82
|
+
return [java()]
|
|
83
|
+
}
|
|
84
|
+
case 'md':
|
|
85
|
+
case 'markdown': {
|
|
86
|
+
const { markdown } = await import('@codemirror/lang-markdown')
|
|
87
|
+
return [markdown()]
|
|
88
|
+
}
|
|
89
|
+
case 'xml':
|
|
90
|
+
case 'svg': {
|
|
91
|
+
const { xml } = await import('@codemirror/lang-xml')
|
|
92
|
+
return [xml()]
|
|
93
|
+
}
|
|
94
|
+
case 'yaml':
|
|
95
|
+
case 'yml': {
|
|
96
|
+
const { yaml } = await import('@codemirror/lang-yaml')
|
|
97
|
+
return [yaml()]
|
|
98
|
+
}
|
|
99
|
+
case 'vue': {
|
|
100
|
+
const { html } = await import('@codemirror/lang-html')
|
|
101
|
+
return [html()]
|
|
102
|
+
}
|
|
103
|
+
case 'sh':
|
|
104
|
+
case 'bash':
|
|
105
|
+
case 'shell':
|
|
106
|
+
case 'zsh': {
|
|
107
|
+
const [{ StreamLanguage }, { shell }] = await Promise.all([
|
|
108
|
+
import('@codemirror/language'),
|
|
109
|
+
import('@codemirror/legacy-modes/mode/shell'),
|
|
110
|
+
])
|
|
111
|
+
return [StreamLanguage.define(shell)]
|
|
112
|
+
}
|
|
113
|
+
default:
|
|
114
|
+
return []
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
return []
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function isCodeMirrorLightTheme(): boolean {
|
|
122
|
+
return typeof document !== 'undefined' && document.documentElement.dataset.rsTheme === 'light'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** 预加载 CodeMirror 核心与目标语言包(供辅助窗口 reveal 前调用) */
|
|
126
|
+
export async function prewarmCodeMirrorEditor(language: string): Promise<void> {
|
|
127
|
+
await Promise.all([import('codemirror'), resolveCodeMirrorLanguage(language)])
|
|
128
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** 右键菜单图标尺寸(与 Dropdown / Menu 一致) */
|
|
2
|
+
export const RS_CONTEXT_MENU_ICON_SIZE = 16
|
|
3
|
+
export const RS_CONTEXT_MENU_ARROW_SIZE = 14
|
|
4
|
+
|
|
5
|
+
/** 右键菜单项定义 */
|
|
6
|
+
export interface RsContextMenuItem {
|
|
7
|
+
/** 唯一标识,选中时回传 */
|
|
8
|
+
key: string
|
|
9
|
+
label: string
|
|
10
|
+
icon?: string
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
/** 危险操作样式 */
|
|
13
|
+
danger?: boolean
|
|
14
|
+
/** 为 true 时渲染分隔线 */
|
|
15
|
+
separator?: boolean
|
|
16
|
+
/** 子菜单项 */
|
|
17
|
+
children?: RsContextMenuItem[]
|
|
18
|
+
/** 键盘快捷键提示(仅展示,不绑定事件),如 "⌘K"、"⌃C" */
|
|
19
|
+
shortcut?: string
|
|
20
|
+
}
|