@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.
Files changed (39) hide show
  1. package/dist/components/action/action-group.vue.d.ts +2 -2
  2. package/dist/components/action/action.vue.d.ts +1 -1
  3. package/dist/components/batch-edit/batch-edit.vue.d.ts +2 -2
  4. package/dist/components/cascade/cascade.vue.d.ts +5 -5
  5. package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
  6. package/dist/components/code-editor/code-editor.js +7 -2
  7. package/dist/components/code-editor/code-editor.js.map +1 -1
  8. package/dist/components/code-editor/lang-loaders.js +2 -1
  9. package/dist/components/code-editor/lang-loaders.js.map +1 -1
  10. package/dist/components/code-editor/langs/spel/completion.js +88 -0
  11. package/dist/components/code-editor/langs/spel/completion.js.map +1 -0
  12. package/dist/components/code-editor/langs/spel/index.js +12 -0
  13. package/dist/components/code-editor/langs/spel/index.js.map +1 -0
  14. package/dist/components/code-editor/langs/spel/language.js +177 -0
  15. package/dist/components/code-editor/langs/spel/language.js.map +1 -0
  16. package/dist/components/code-editor/style2.css +11 -0
  17. package/dist/components/file-picker/file-picker.vue.d.ts +1 -1
  18. package/dist/components/input/input.vue.d.ts +2 -2
  19. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  20. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +5 -5
  21. package/dist/components/number-input/number-input.vue.d.ts +2 -2
  22. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  23. package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
  24. package/dist/components/select/select.vue.d.ts +2 -2
  25. package/dist/components/steps/steps.vue.d.ts +1 -1
  26. package/dist/components/textarea/textarea.vue.d.ts +2 -2
  27. package/dist/components/tip/tip.vue.d.ts +2 -2
  28. package/dist/components/tree-select/tree-select.vue.d.ts +5 -5
  29. package/dist/node_modules/.bun/@codemirror_language@6.12.4/node_modules/@codemirror/language/dist/index.js +452 -2
  30. package/dist/node_modules/.bun/@codemirror_language@6.12.4/node_modules/@codemirror/language/dist/index.js.map +1 -1
  31. package/dist/types/code-editor.d.ts +3 -2
  32. package/package.json +6 -6
  33. package/src/components/code-editor/code-editor.vue +6 -0
  34. package/src/components/code-editor/lang-loaders.ts +2 -1
  35. package/src/components/code-editor/langs/spel/completion.ts +30 -0
  36. package/src/components/code-editor/langs/spel/index.ts +12 -0
  37. package/src/components/code-editor/langs/spel/language.ts +198 -0
  38. package/src/components/code-editor/style.scss +12 -0
  39. 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.28",
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.28",
61
- "@veltra/directives": "1.2.28",
62
- "@veltra/icons": "1.2.28",
63
- "@veltra/styles": "1.2.28",
64
- "@veltra/utils": "1.2.28",
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);
@@ -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` 使用 */