@veltra/desktop 1.2.28 → 1.2.29
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/dist/components/action/action-group.vue.d.ts +2 -2
- package/dist/components/action/action.vue.d.ts +1 -1
- package/dist/components/batch-edit/batch-edit.vue.d.ts +2 -2
- package/dist/components/cascade/cascade.vue.d.ts +5 -5
- package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
- package/dist/components/code-editor/code-editor.js +7 -2
- package/dist/components/code-editor/code-editor.js.map +1 -1
- package/dist/components/code-editor/lang-loaders.js +2 -1
- package/dist/components/code-editor/lang-loaders.js.map +1 -1
- package/dist/components/code-editor/langs/spel/completion.js +88 -0
- package/dist/components/code-editor/langs/spel/completion.js.map +1 -0
- package/dist/components/code-editor/langs/spel/index.js +12 -0
- package/dist/components/code-editor/langs/spel/index.js.map +1 -0
- package/dist/components/code-editor/langs/spel/language.js +177 -0
- package/dist/components/code-editor/langs/spel/language.js.map +1 -0
- package/dist/components/code-editor/style2.css +11 -0
- package/dist/components/file-picker/file-picker.vue.d.ts +1 -1
- package/dist/components/input/input.vue.d.ts +2 -2
- package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
- package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +5 -5
- package/dist/components/number-input/number-input.vue.d.ts +2 -2
- package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
- package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
- package/dist/components/select/select.vue.d.ts +2 -2
- package/dist/components/steps/steps.vue.d.ts +1 -1
- package/dist/components/textarea/textarea.vue.d.ts +2 -2
- package/dist/components/tip/tip.vue.d.ts +2 -2
- package/dist/components/tree-select/tree-select.vue.d.ts +5 -5
- package/dist/node_modules/.bun/@codemirror_language@6.12.4/node_modules/@codemirror/language/dist/index.js +452 -2
- package/dist/node_modules/.bun/@codemirror_language@6.12.4/node_modules/@codemirror/language/dist/index.js.map +1 -1
- package/dist/types/code-editor.d.ts +3 -2
- package/package.json +6 -6
- package/src/components/code-editor/code-editor.vue +6 -0
- package/src/components/code-editor/lang-loaders.ts +2 -1
- package/src/components/code-editor/langs/spel/completion.ts +30 -0
- package/src/components/code-editor/langs/spel/index.ts +12 -0
- package/src/components/code-editor/langs/spel/language.ts +198 -0
- package/src/components/code-editor/style.scss +12 -0
- package/src/types/code-editor.ts +3 -2
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { DeconstructValue, FormComponentProps } from "@veltra/utils";
|
|
2
2
|
|
|
3
3
|
//#region src/types/code-editor.d.ts
|
|
4
|
-
type CodeEditorLang = 'js' | 'sql' | 'java' | 'json';
|
|
4
|
+
type CodeEditorLang = 'js' | 'sql' | 'java' | 'json' | 'spel';
|
|
5
5
|
/** 代码编辑器组件属性(不支持 `size`) */
|
|
6
6
|
interface CodeEditorProps extends Omit<FormComponentProps, 'size'> {
|
|
7
7
|
modelValue?: string;
|
|
8
8
|
/**
|
|
9
|
-
* 可选语言列表。长度大于 1
|
|
9
|
+
* 可选语言列表。长度大于 1 时在编辑器右上角显示内置语言选择器;
|
|
10
|
+
* 仅一种语言时显示语言名称标签
|
|
10
11
|
*/
|
|
11
12
|
langs?: CodeEditorLang[];
|
|
12
13
|
/** 当前语言,配合 `v-model:lang` 使用 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veltra/desktop",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.29",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"src"
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"@cat-kit/core": ">=1.1.8",
|
|
58
58
|
"@cat-kit/excel": ">=1.0.0",
|
|
59
59
|
"@cat-kit/fe": "^1.1.8",
|
|
60
|
-
"@veltra/compositions": "1.2.
|
|
61
|
-
"@veltra/directives": "1.2.
|
|
62
|
-
"@veltra/icons": "1.2.
|
|
63
|
-
"@veltra/styles": "1.2.
|
|
64
|
-
"@veltra/utils": "1.2.
|
|
60
|
+
"@veltra/compositions": "1.2.29",
|
|
61
|
+
"@veltra/directives": "1.2.29",
|
|
62
|
+
"@veltra/icons": "1.2.29",
|
|
63
|
+
"@veltra/styles": "1.2.29",
|
|
64
|
+
"@veltra/utils": "1.2.29",
|
|
65
65
|
"vue": ">=3.5.39"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
:clearable="false"
|
|
12
12
|
:disabled="disabled"
|
|
13
13
|
/>
|
|
14
|
+
<span v-else-if="langLabel" :class="cls.e('lang-label')">{{ langLabel }}</span>
|
|
14
15
|
</u-scroll>
|
|
15
16
|
</template>
|
|
16
17
|
|
|
@@ -89,6 +90,11 @@ const langOptions = computed(() =>
|
|
|
89
90
|
/** 实际生效的语言:优先 lang model,否则回落 langs[0] */
|
|
90
91
|
const activeLang = computed<CodeEditorLang | undefined>(() => lang.value ?? langs.value[0])
|
|
91
92
|
|
|
93
|
+
/** 仅一种语言时,在选择器位置展示名称 */
|
|
94
|
+
const langLabel = computed(() =>
|
|
95
|
+
langs.value.length === 1 && activeLang.value ? activeLang.value.toUpperCase() : undefined
|
|
96
|
+
)
|
|
97
|
+
|
|
92
98
|
const shellConfig = computed<ShellConfig>(() => ({
|
|
93
99
|
prefix: props.prefix ?? '',
|
|
94
100
|
suffix: props.suffix ?? ''
|
|
@@ -7,7 +7,8 @@ const LANG_LOADERS: Record<CodeEditorLang, () => Promise<Extension>> = {
|
|
|
7
7
|
js: () => import('@codemirror/lang-javascript').then((m) => m.javascript({ typescript: true })),
|
|
8
8
|
sql: () => import('@codemirror/lang-sql').then((m) => m.sql()),
|
|
9
9
|
java: () => import('@codemirror/lang-java').then((m) => m.java()),
|
|
10
|
-
json: () => import('@codemirror/lang-json').then((m) => m.json())
|
|
10
|
+
json: () => import('@codemirror/lang-json').then((m) => m.json()),
|
|
11
|
+
spel: () => import('./langs/spel').then((m) => m.spel())
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/** 按需加载语言扩展;模块缓存由运行时保证,重复切换同一语言不会重复下载 */
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { completeFromList, type Completion } from '@codemirror/autocomplete'
|
|
2
|
+
|
|
3
|
+
/** SpEL 关键字与常用内置标识补全 */
|
|
4
|
+
const SPEL_COMPLETIONS: Completion[] = [
|
|
5
|
+
// 字面量
|
|
6
|
+
{ label: 'true', type: 'constant' },
|
|
7
|
+
{ label: 'false', type: 'constant' },
|
|
8
|
+
{ label: 'null', type: 'constant' },
|
|
9
|
+
// 关键字 / 运算符词
|
|
10
|
+
{ label: 'and', type: 'keyword' },
|
|
11
|
+
{ label: 'or', type: 'keyword' },
|
|
12
|
+
{ label: 'not', type: 'keyword' },
|
|
13
|
+
{ label: 'eq', type: 'keyword' },
|
|
14
|
+
{ label: 'ne', type: 'keyword' },
|
|
15
|
+
{ label: 'lt', type: 'keyword' },
|
|
16
|
+
{ label: 'le', type: 'keyword' },
|
|
17
|
+
{ label: 'gt', type: 'keyword' },
|
|
18
|
+
{ label: 'ge', type: 'keyword' },
|
|
19
|
+
{ label: 'matches', type: 'keyword' },
|
|
20
|
+
{ label: 'between', type: 'keyword' },
|
|
21
|
+
{ label: 'instanceof', type: 'keyword' },
|
|
22
|
+
{ label: 'new', type: 'keyword' },
|
|
23
|
+
// 类型引用与上下文变量
|
|
24
|
+
{ label: 'T', type: 'type', detail: 'T(Type)', info: '类型引用' },
|
|
25
|
+
{ label: '#this', type: 'variable', detail: '当前上下文' },
|
|
26
|
+
{ label: '#root', type: 'variable', detail: '根对象' }
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
/** 基于关键字列表的补全源 */
|
|
30
|
+
export const spelCompletion = completeFromList(SPEL_COMPLETIONS)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LanguageSupport } from '@codemirror/language'
|
|
2
|
+
|
|
3
|
+
import { spelCompletion } from './completion'
|
|
4
|
+
import { spelLanguage } from './language'
|
|
5
|
+
|
|
6
|
+
/** SpEL(Spring Expression Language)语言支持:语法高亮 + 基础补全 */
|
|
7
|
+
export function spel(): LanguageSupport {
|
|
8
|
+
return new LanguageSupport(spelLanguage, [spelLanguage.data.of({ autocomplete: spelCompletion })])
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { spelLanguage } from './language'
|
|
12
|
+
export { spelCompletion } from './completion'
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { StreamLanguage, type StringStream } from '@codemirror/language'
|
|
2
|
+
|
|
3
|
+
/** 词法状态:跟踪 T(...) 类型引用与属性访问 */
|
|
4
|
+
interface SpelState {
|
|
5
|
+
/** T(...) 内括号深度,>0 表示处于类型名上下文 */
|
|
6
|
+
typeDepth: number
|
|
7
|
+
/** 刚消费完 `.`,下一标识符按属性名着色 */
|
|
8
|
+
afterDot: boolean
|
|
9
|
+
/** 刚消费完类型引用关键字 `T`,下一 `(` 进入类型上下文 */
|
|
10
|
+
afterT: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const KEYWORDS = new Set([
|
|
14
|
+
'new',
|
|
15
|
+
'instanceof',
|
|
16
|
+
'matches',
|
|
17
|
+
'between',
|
|
18
|
+
'and',
|
|
19
|
+
'or',
|
|
20
|
+
'not',
|
|
21
|
+
'eq',
|
|
22
|
+
'ne',
|
|
23
|
+
'lt',
|
|
24
|
+
'le',
|
|
25
|
+
'gt',
|
|
26
|
+
'ge'
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
const BOOL_LITERALS = new Set(['true', 'false'])
|
|
30
|
+
|
|
31
|
+
/** 多字符运算符,按长度优先匹配 */
|
|
32
|
+
const OPERATORS = ['?.', '?:', '?[', '![', '==', '!=', '<=', '>=', '&&', '||', '++', '--']
|
|
33
|
+
|
|
34
|
+
function isIdentStart(ch: string): boolean {
|
|
35
|
+
return /[A-Za-z_]/.test(ch)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isIdentChar(ch: string): boolean {
|
|
39
|
+
return /[A-Za-z0-9_]/.test(ch)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function tokenString(stream: StringStream, quote: string): string {
|
|
43
|
+
let escaped = false
|
|
44
|
+
while (!stream.eol()) {
|
|
45
|
+
const next = stream.next()
|
|
46
|
+
if (next === quote && !escaped) break
|
|
47
|
+
escaped = !escaped && next === '\\'
|
|
48
|
+
}
|
|
49
|
+
return 'string'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function tokenNumber(stream: StringStream): string {
|
|
53
|
+
stream.match(/^\d+(\.\d+)?([eE][+-]?\d+)?/)
|
|
54
|
+
return 'number'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function tokenIdentifier(stream: StringStream, state: SpelState): string {
|
|
58
|
+
stream.eatWhile(isIdentChar)
|
|
59
|
+
const word = stream.current()
|
|
60
|
+
state.afterT = false
|
|
61
|
+
|
|
62
|
+
if (state.typeDepth > 0) {
|
|
63
|
+
state.afterDot = false
|
|
64
|
+
return 'typeName'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (state.afterDot) {
|
|
68
|
+
state.afterDot = false
|
|
69
|
+
return 'propertyName'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (BOOL_LITERALS.has(word)) return 'bool'
|
|
73
|
+
if (word === 'null') return 'null'
|
|
74
|
+
if (KEYWORDS.has(word)) return 'keyword'
|
|
75
|
+
|
|
76
|
+
// T(...) 类型引用
|
|
77
|
+
if (word === 'T' && stream.peek() === '(') {
|
|
78
|
+
state.afterT = true
|
|
79
|
+
return 'typeName'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return 'name'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function tokenSpel(stream: StringStream, state: SpelState): string | null {
|
|
86
|
+
if (stream.eatSpace()) return null
|
|
87
|
+
|
|
88
|
+
const ch = stream.peek()
|
|
89
|
+
if (!ch) return null
|
|
90
|
+
|
|
91
|
+
// 字符串
|
|
92
|
+
if (ch === "'" || ch === '"') {
|
|
93
|
+
stream.next()
|
|
94
|
+
state.afterDot = false
|
|
95
|
+
state.afterT = false
|
|
96
|
+
return tokenString(stream, ch)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 数字
|
|
100
|
+
if (/\d/.test(ch)) {
|
|
101
|
+
state.afterDot = false
|
|
102
|
+
state.afterT = false
|
|
103
|
+
return tokenNumber(stream)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// #变量 / #this / #root
|
|
107
|
+
if (ch === '#') {
|
|
108
|
+
stream.next()
|
|
109
|
+
state.afterDot = false
|
|
110
|
+
state.afterT = false
|
|
111
|
+
const peek = stream.peek()
|
|
112
|
+
if (peek && isIdentStart(peek)) {
|
|
113
|
+
stream.eatWhile(isIdentChar)
|
|
114
|
+
return 'variableName'
|
|
115
|
+
}
|
|
116
|
+
return 'operator'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 多字符运算符
|
|
120
|
+
for (const op of OPERATORS) {
|
|
121
|
+
if (stream.match(op)) {
|
|
122
|
+
state.afterDot = false
|
|
123
|
+
state.afterT = false
|
|
124
|
+
return 'operator'
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 单字符运算符
|
|
129
|
+
if ('+-*/%^!=<>&|?:'.includes(ch)) {
|
|
130
|
+
stream.next()
|
|
131
|
+
state.afterDot = false
|
|
132
|
+
state.afterT = false
|
|
133
|
+
return 'operator'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (ch === '(') {
|
|
137
|
+
stream.next()
|
|
138
|
+
state.afterDot = false
|
|
139
|
+
if (state.afterT || state.typeDepth > 0) {
|
|
140
|
+
state.typeDepth++
|
|
141
|
+
state.afterT = false
|
|
142
|
+
}
|
|
143
|
+
return 'paren'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (ch === ')') {
|
|
147
|
+
stream.next()
|
|
148
|
+
state.afterDot = false
|
|
149
|
+
state.afterT = false
|
|
150
|
+
if (state.typeDepth > 0) state.typeDepth--
|
|
151
|
+
return 'paren'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (ch === '[' || ch === ']') {
|
|
155
|
+
stream.next()
|
|
156
|
+
state.afterDot = false
|
|
157
|
+
state.afterT = false
|
|
158
|
+
return 'paren'
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (ch === '{' || ch === '}') {
|
|
162
|
+
stream.next()
|
|
163
|
+
state.afterDot = false
|
|
164
|
+
state.afterT = false
|
|
165
|
+
return 'brace'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (ch === ',') {
|
|
169
|
+
stream.next()
|
|
170
|
+
state.afterDot = false
|
|
171
|
+
state.afterT = false
|
|
172
|
+
return 'punctuation'
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (ch === '.') {
|
|
176
|
+
stream.next()
|
|
177
|
+
state.afterDot = true
|
|
178
|
+
state.afterT = false
|
|
179
|
+
return 'punctuation'
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (isIdentStart(ch)) {
|
|
183
|
+
return tokenIdentifier(stream, state)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
stream.next()
|
|
187
|
+
state.afterDot = false
|
|
188
|
+
state.afterT = false
|
|
189
|
+
return 'punctuation'
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** SpEL StreamLanguage */
|
|
193
|
+
export const spelLanguage = StreamLanguage.define<SpelState>({
|
|
194
|
+
name: 'spel',
|
|
195
|
+
startState: () => ({ typeDepth: 0, afterDot: false, afterT: false }),
|
|
196
|
+
token: tokenSpel,
|
|
197
|
+
languageData: { closeBrackets: { brackets: ['(', '[', '{', "'", '"'] } }
|
|
198
|
+
})
|
|
@@ -45,6 +45,18 @@ $root-name: code-editor;
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
@include m.e(lang-label) {
|
|
49
|
+
position: absolute;
|
|
50
|
+
top: 2px;
|
|
51
|
+
right: 8px;
|
|
52
|
+
z-index: 2;
|
|
53
|
+
font-size: 12px;
|
|
54
|
+
line-height: 24px;
|
|
55
|
+
color: var(--u-code-editor-gutter-fg);
|
|
56
|
+
pointer-events: none;
|
|
57
|
+
user-select: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
48
60
|
.cm-editor {
|
|
49
61
|
background: transparent;
|
|
50
62
|
color: var(--u-code-editor-fg);
|
package/src/types/code-editor.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { DeconstructValue, FormComponentProps } from '@veltra/utils'
|
|
2
2
|
|
|
3
|
-
export type CodeEditorLang = 'js' | 'sql' | 'java' | 'json'
|
|
3
|
+
export type CodeEditorLang = 'js' | 'sql' | 'java' | 'json' | 'spel'
|
|
4
4
|
|
|
5
5
|
/** 代码编辑器组件属性(不支持 `size`) */
|
|
6
6
|
export interface CodeEditorProps extends Omit<FormComponentProps, 'size'> {
|
|
7
7
|
modelValue?: string
|
|
8
8
|
/**
|
|
9
|
-
* 可选语言列表。长度大于 1
|
|
9
|
+
* 可选语言列表。长度大于 1 时在编辑器右上角显示内置语言选择器;
|
|
10
|
+
* 仅一种语言时显示语言名称标签
|
|
10
11
|
*/
|
|
11
12
|
langs?: CodeEditorLang[]
|
|
12
13
|
/** 当前语言,配合 `v-model:lang` 使用 */
|