mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-06-01 21:20:47 +00:00
feat: init docs by vitepress
This commit is contained in:
parent
a7820908d7
commit
9e08c2a224
3
.gitignore
vendored
3
.gitignore
vendored
@ -103,4 +103,5 @@ typings/
|
|||||||
codealike.json
|
codealike.json
|
||||||
.node
|
.node
|
||||||
|
|
||||||
.must.config.js
|
# docs
|
||||||
|
.vitepress
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 100,
|
"printWidth": 120,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"jsxSingleQuote": false,
|
"jsxSingleQuote": false,
|
||||||
|
|||||||
4
TODOS.md
4
TODOS.md
@ -12,3 +12,7 @@
|
|||||||
7. github workflows
|
7. github workflows
|
||||||
|
|
||||||
lodaes replace
|
lodaes replace
|
||||||
|
|
||||||
|
IDE 的引擎设计
|
||||||
|
工作区的虚拟文件设计
|
||||||
|
窗口、视图、编辑器、边栏、面板的设计
|
||||||
|
|||||||
49
docs/api-examples.md
Normal file
49
docs/api-examples.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Runtime API Examples
|
||||||
|
|
||||||
|
This page demonstrates usage of some of the runtime APIs provided by VitePress.
|
||||||
|
|
||||||
|
The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
|
||||||
|
|
||||||
|
```md
|
||||||
|
<script setup>
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
|
||||||
|
const { theme, page, frontmatter } = useData()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
### Theme Data
|
||||||
|
<pre>{{ theme }}</pre>
|
||||||
|
|
||||||
|
### Page Data
|
||||||
|
<pre>{{ page }}</pre>
|
||||||
|
|
||||||
|
### Page Frontmatter
|
||||||
|
<pre>{{ frontmatter }}</pre>
|
||||||
|
```
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
|
||||||
|
const { site, theme, page, frontmatter } = useData()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
### Theme Data
|
||||||
|
<pre>{{ theme }}</pre>
|
||||||
|
|
||||||
|
### Page Data
|
||||||
|
<pre>{{ page }}</pre>
|
||||||
|
|
||||||
|
### Page Frontmatter
|
||||||
|
<pre>{{ frontmatter }}</pre>
|
||||||
|
|
||||||
|
## More
|
||||||
|
|
||||||
|
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
|
||||||
0
docs/api/index.md
Normal file
0
docs/api/index.md
Normal file
8
docs/examples/hello-world.md
Normal file
8
docs/examples/hello-world.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 你好,世界
|
||||||
|
|
||||||
|
step1: 启动引擎,新建一个项目,新建一个低代码文件(.lc)
|
||||||
|
step2: 打开画布
|
||||||
|
step3: 拖拽组件
|
||||||
|
step4: 点击保存 (cmd + s)
|
||||||
|
step5: 点击预览 (cmd + p)
|
||||||
|
step6: 点击关闭,重新打开这个页面,内容不变
|
||||||
0
docs/guide/introduction.md
Normal file
0
docs/guide/introduction.md
Normal file
0
docs/guide/quick-start.md
Normal file
0
docs/guide/quick-start.md
Normal file
22
docs/index.md
Normal file
22
docs/index.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: 'LowCodeEngine'
|
||||||
|
text: '基于 LowCodeEngine 快速打造高生产力的低代码研发平台'
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: 介绍
|
||||||
|
link: /guide/introduction
|
||||||
|
- theme: alt
|
||||||
|
text: 快速开始
|
||||||
|
link: /guide/quick-start
|
||||||
|
|
||||||
|
features:
|
||||||
|
- title: 标准化协议
|
||||||
|
details: 底层协议栈定义的是标准,标准的统一让上层产物的互通成为可能
|
||||||
|
- title: 最小内核
|
||||||
|
details: 精心打造低代码领域的编排、入料、出码、渲染模块
|
||||||
|
- title: 最强生态
|
||||||
|
details: 配套生态开箱即用,打造企业级低代码技术体系
|
||||||
|
---
|
||||||
85
docs/markdown-examples.md
Normal file
85
docs/markdown-examples.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Markdown Extension Examples
|
||||||
|
|
||||||
|
This page demonstrates some of the built-in markdown extensions provided by VitePress.
|
||||||
|
|
||||||
|
## Syntax Highlighting
|
||||||
|
|
||||||
|
VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting:
|
||||||
|
|
||||||
|
**Input**
|
||||||
|
|
||||||
|
````md
|
||||||
|
```js{4}
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
msg: 'Highlighted!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
|
||||||
|
```js{4}
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
msg: 'Highlighted!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Containers
|
||||||
|
|
||||||
|
**Input**
|
||||||
|
|
||||||
|
```md
|
||||||
|
::: info
|
||||||
|
This is an info box.
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
This is a tip.
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
This is a warning.
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: danger
|
||||||
|
This is a dangerous warning.
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: details
|
||||||
|
This is a details block.
|
||||||
|
:::
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
|
||||||
|
::: info
|
||||||
|
This is an info box.
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
This is a tip.
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
This is a warning.
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: danger
|
||||||
|
This is a dangerous warning.
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: details
|
||||||
|
This is a details block.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## More
|
||||||
|
|
||||||
|
Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown).
|
||||||
23
docs/package.json
Normal file
23
docs/package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@alilc/lowcode-engine-docs",
|
||||||
|
"version": "2.0.0-alpha.0",
|
||||||
|
"description": "低代码引擎版本化文档",
|
||||||
|
"keywords": [
|
||||||
|
"lowcode",
|
||||||
|
"engine",
|
||||||
|
"docs"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"docs:dev": "vitepress dev .",
|
||||||
|
"docs:build": "vitepress build .",
|
||||||
|
"docs:preview": "vitepress preview ."
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "https://github.com/alibaba/lowcode-engine/tree/main"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"vitepress": "^1.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
docs/public/logo.svg
Normal file
1
docs/public/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.3 KiB |
@ -1,12 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
type Event,
|
Events,
|
||||||
type EventDisposable,
|
|
||||||
type EventListener,
|
|
||||||
Emitter,
|
|
||||||
LinkedList,
|
LinkedList,
|
||||||
TypeConstraint,
|
TypeConstraint,
|
||||||
validateConstraints,
|
validateConstraints,
|
||||||
Iterable,
|
Iterable,
|
||||||
|
IDisposable,
|
||||||
|
Disposable,
|
||||||
|
toDisposable,
|
||||||
} from '@alilc/lowcode-shared';
|
} from '@alilc/lowcode-shared';
|
||||||
import { ICommand, ICommandHandler } from './command';
|
import { ICommand, ICommandHandler } from './command';
|
||||||
import { Extensions, Registry } from '../extension/registry';
|
import { Extensions, Registry } from '../extension/registry';
|
||||||
@ -15,27 +15,24 @@ import { ICommandService } from './commandService';
|
|||||||
export type ICommandsMap = Map<string, ICommand>;
|
export type ICommandsMap = Map<string, ICommand>;
|
||||||
|
|
||||||
export interface ICommandRegistry {
|
export interface ICommandRegistry {
|
||||||
onDidRegisterCommand: Event<string>;
|
onDidRegisterCommand: Events.Event<string>;
|
||||||
|
|
||||||
registerCommand(id: string, command: ICommandHandler): EventDisposable;
|
registerCommand(id: string, command: ICommandHandler): IDisposable;
|
||||||
registerCommand(command: ICommand): EventDisposable;
|
registerCommand(command: ICommand): IDisposable;
|
||||||
|
|
||||||
registerCommandAlias(oldId: string, newId: string): EventDisposable;
|
registerCommandAlias(oldId: string, newId: string): IDisposable;
|
||||||
|
|
||||||
getCommand(id: string): ICommand | undefined;
|
getCommand(id: string): ICommand | undefined;
|
||||||
getCommands(): ICommandsMap;
|
getCommands(): ICommandsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommandsRegistryImpl implements ICommandRegistry {
|
class CommandsRegistryImpl extends Disposable implements ICommandRegistry {
|
||||||
private readonly _commands = new Map<string, LinkedList<ICommand>>();
|
private readonly _commands = new Map<string, LinkedList<ICommand>>();
|
||||||
|
|
||||||
private readonly _didRegisterCommandEmitter = new Emitter<string>();
|
private readonly _onDidRegisterCommand = this._addDispose(new Events.Emitter<string>());
|
||||||
|
onDidRegisterCommand = this._onDidRegisterCommand.event;
|
||||||
|
|
||||||
onDidRegisterCommand(fn: EventListener<string>) {
|
registerCommand(idOrCommand: string | ICommand, handler?: ICommandHandler): IDisposable {
|
||||||
return this._didRegisterCommandEmitter.on(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCommand(idOrCommand: string | ICommand, handler?: ICommandHandler): EventDisposable {
|
|
||||||
if (!idOrCommand) {
|
if (!idOrCommand) {
|
||||||
throw new Error(`invalid command`);
|
throw new Error(`invalid command`);
|
||||||
}
|
}
|
||||||
@ -71,21 +68,21 @@ class CommandsRegistryImpl implements ICommandRegistry {
|
|||||||
|
|
||||||
const removeFn = commands.unshift(idOrCommand);
|
const removeFn = commands.unshift(idOrCommand);
|
||||||
|
|
||||||
const ret = () => {
|
const ret = toDisposable(() => {
|
||||||
removeFn();
|
removeFn();
|
||||||
const command = this._commands.get(id);
|
const command = this._commands.get(id);
|
||||||
if (command?.isEmpty()) {
|
if (command?.isEmpty()) {
|
||||||
this._commands.delete(id);
|
this._commands.delete(id);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
// tell the world about this command
|
// tell the world about this command
|
||||||
this._didRegisterCommandEmitter.emit(id);
|
this._onDidRegisterCommand.notify(id);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCommandAlias(oldId: string, newId: string): EventDisposable {
|
registerCommandAlias(oldId: string, newId: string): IDisposable {
|
||||||
return this.registerCommand(oldId, (accessor, ...args) =>
|
return this.registerCommand(oldId, (accessor, ...args) =>
|
||||||
accessor.get(ICommandService).executeCommand(newId, ...args),
|
accessor.get(ICommandService).executeCommand(newId, ...args),
|
||||||
);
|
);
|
||||||
|
|||||||
228
packages/engine-core/src/common/charCode.ts
Normal file
228
packages/engine-core/src/common/charCode.ts
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An inlined enum containing useful character codes (to be used with String.charCodeAt).
|
||||||
|
* Please leave the const keyword such that it gets inlined when compiled to JavaScript!
|
||||||
|
*/
|
||||||
|
export const enum CharCode {
|
||||||
|
Null = 0,
|
||||||
|
/**
|
||||||
|
* The `\b` character.
|
||||||
|
*/
|
||||||
|
Backspace = 8,
|
||||||
|
/**
|
||||||
|
* The `\t` character.
|
||||||
|
*/
|
||||||
|
Tab = 9,
|
||||||
|
/**
|
||||||
|
* The `\n` character.
|
||||||
|
*/
|
||||||
|
LineFeed = 10,
|
||||||
|
/**
|
||||||
|
* The `\r` character.
|
||||||
|
*/
|
||||||
|
CarriageReturn = 13,
|
||||||
|
Space = 32,
|
||||||
|
/**
|
||||||
|
* The `!` character.
|
||||||
|
*/
|
||||||
|
ExclamationMark = 33,
|
||||||
|
/**
|
||||||
|
* The `"` character.
|
||||||
|
*/
|
||||||
|
DoubleQuote = 34,
|
||||||
|
/**
|
||||||
|
* The `#` character.
|
||||||
|
*/
|
||||||
|
Hash = 35,
|
||||||
|
/**
|
||||||
|
* The `$` character.
|
||||||
|
*/
|
||||||
|
DollarSign = 36,
|
||||||
|
/**
|
||||||
|
* The `%` character.
|
||||||
|
*/
|
||||||
|
PercentSign = 37,
|
||||||
|
/**
|
||||||
|
* The `&` character.
|
||||||
|
*/
|
||||||
|
Ampersand = 38,
|
||||||
|
/**
|
||||||
|
* The `'` character.
|
||||||
|
*/
|
||||||
|
SingleQuote = 39,
|
||||||
|
/**
|
||||||
|
* The `(` character.
|
||||||
|
*/
|
||||||
|
OpenParen = 40,
|
||||||
|
/**
|
||||||
|
* The `)` character.
|
||||||
|
*/
|
||||||
|
CloseParen = 41,
|
||||||
|
/**
|
||||||
|
* The `*` character.
|
||||||
|
*/
|
||||||
|
Asterisk = 42,
|
||||||
|
/**
|
||||||
|
* The `+` character.
|
||||||
|
*/
|
||||||
|
Plus = 43,
|
||||||
|
/**
|
||||||
|
* The `,` character.
|
||||||
|
*/
|
||||||
|
Comma = 44,
|
||||||
|
/**
|
||||||
|
* The `-` character.
|
||||||
|
*/
|
||||||
|
Dash = 45,
|
||||||
|
/**
|
||||||
|
* The `.` character.
|
||||||
|
*/
|
||||||
|
Period = 46,
|
||||||
|
/**
|
||||||
|
* The `/` character.
|
||||||
|
*/
|
||||||
|
Slash = 47,
|
||||||
|
|
||||||
|
Digit0 = 48,
|
||||||
|
Digit1 = 49,
|
||||||
|
Digit2 = 50,
|
||||||
|
Digit3 = 51,
|
||||||
|
Digit4 = 52,
|
||||||
|
Digit5 = 53,
|
||||||
|
Digit6 = 54,
|
||||||
|
Digit7 = 55,
|
||||||
|
Digit8 = 56,
|
||||||
|
Digit9 = 57,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `:` character.
|
||||||
|
*/
|
||||||
|
Colon = 58,
|
||||||
|
/**
|
||||||
|
* The `;` character.
|
||||||
|
*/
|
||||||
|
Semicolon = 59,
|
||||||
|
/**
|
||||||
|
* The `<` character.
|
||||||
|
*/
|
||||||
|
LessThan = 60,
|
||||||
|
/**
|
||||||
|
* The `=` character.
|
||||||
|
*/
|
||||||
|
Equals = 61,
|
||||||
|
/**
|
||||||
|
* The `>` character.
|
||||||
|
*/
|
||||||
|
GreaterThan = 62,
|
||||||
|
/**
|
||||||
|
* The `?` character.
|
||||||
|
*/
|
||||||
|
QuestionMark = 63,
|
||||||
|
/**
|
||||||
|
* The `@` character.
|
||||||
|
*/
|
||||||
|
AtSign = 64,
|
||||||
|
|
||||||
|
A = 65,
|
||||||
|
B = 66,
|
||||||
|
C = 67,
|
||||||
|
D = 68,
|
||||||
|
E = 69,
|
||||||
|
F = 70,
|
||||||
|
G = 71,
|
||||||
|
H = 72,
|
||||||
|
I = 73,
|
||||||
|
J = 74,
|
||||||
|
K = 75,
|
||||||
|
L = 76,
|
||||||
|
M = 77,
|
||||||
|
N = 78,
|
||||||
|
O = 79,
|
||||||
|
P = 80,
|
||||||
|
Q = 81,
|
||||||
|
R = 82,
|
||||||
|
S = 83,
|
||||||
|
T = 84,
|
||||||
|
U = 85,
|
||||||
|
V = 86,
|
||||||
|
W = 87,
|
||||||
|
X = 88,
|
||||||
|
Y = 89,
|
||||||
|
Z = 90,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `[` character.
|
||||||
|
*/
|
||||||
|
OpenSquareBracket = 91,
|
||||||
|
/**
|
||||||
|
* The `\` character.
|
||||||
|
*/
|
||||||
|
Backslash = 92,
|
||||||
|
/**
|
||||||
|
* The `]` character.
|
||||||
|
*/
|
||||||
|
CloseSquareBracket = 93,
|
||||||
|
/**
|
||||||
|
* The `^` character.
|
||||||
|
*/
|
||||||
|
Caret = 94,
|
||||||
|
/**
|
||||||
|
* The `_` character.
|
||||||
|
*/
|
||||||
|
Underline = 95,
|
||||||
|
/**
|
||||||
|
* The ``(`)`` character.
|
||||||
|
*/
|
||||||
|
BackTick = 96,
|
||||||
|
|
||||||
|
a = 97,
|
||||||
|
b = 98,
|
||||||
|
c = 99,
|
||||||
|
d = 100,
|
||||||
|
e = 101,
|
||||||
|
f = 102,
|
||||||
|
g = 103,
|
||||||
|
h = 104,
|
||||||
|
i = 105,
|
||||||
|
j = 106,
|
||||||
|
k = 107,
|
||||||
|
l = 108,
|
||||||
|
m = 109,
|
||||||
|
n = 110,
|
||||||
|
o = 111,
|
||||||
|
p = 112,
|
||||||
|
q = 113,
|
||||||
|
r = 114,
|
||||||
|
s = 115,
|
||||||
|
t = 116,
|
||||||
|
u = 117,
|
||||||
|
v = 118,
|
||||||
|
w = 119,
|
||||||
|
x = 120,
|
||||||
|
y = 121,
|
||||||
|
z = 122,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `{` character.
|
||||||
|
*/
|
||||||
|
OpenCurlyBrace = 123,
|
||||||
|
/**
|
||||||
|
* The `|` character.
|
||||||
|
*/
|
||||||
|
Pipe = 124,
|
||||||
|
/**
|
||||||
|
* The `}` character.
|
||||||
|
*/
|
||||||
|
CloseCurlyBrace = 125,
|
||||||
|
/**
|
||||||
|
* The `~` character.
|
||||||
|
*/
|
||||||
|
Tilde = 126,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The (no-break space) character.
|
||||||
|
* Unicode Character 'NO-BREAK SPACE' (U+00A0)
|
||||||
|
*/
|
||||||
|
NoBreakSpace = 160,
|
||||||
|
}
|
||||||
23
packages/engine-core/src/common/glob.ts
Normal file
23
packages/engine-core/src/common/glob.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export interface IRelativePattern {
|
||||||
|
/**
|
||||||
|
* A base file path to which this pattern will be matched against relatively.
|
||||||
|
*/
|
||||||
|
readonly base: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file glob pattern like `*.{ts,js}` that will be matched on file paths
|
||||||
|
* relative to the base path.
|
||||||
|
*
|
||||||
|
* Example: Given a base of `/home/work/folder` and a file path of `/home/work/folder/index.js`,
|
||||||
|
* the file glob pattern will match on `index.js`.
|
||||||
|
*/
|
||||||
|
readonly pattern: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IExpression {
|
||||||
|
[pattern: string]: boolean | SiblingClause;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SiblingClause {
|
||||||
|
when: string;
|
||||||
|
}
|
||||||
11
packages/engine-core/src/common/index.ts
Normal file
11
packages/engine-core/src/common/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as Schemas from './schemas';
|
||||||
|
|
||||||
|
export { Schemas };
|
||||||
|
|
||||||
|
export * from './charCode';
|
||||||
|
export * from './glob';
|
||||||
|
export * from './keyCodes';
|
||||||
|
export * from './path';
|
||||||
|
export * from './strings';
|
||||||
|
export * from './ternarySearchTree';
|
||||||
|
export * from './uri';
|
||||||
@ -1243,50 +1243,21 @@ for (let i = 0; i <= KeyCode.MAX_VALUE; i++) {
|
|||||||
IMMUTABLE_KEY_CODE_TO_CODE[KeyCode.Enter] = ScanCode.Enter;
|
IMMUTABLE_KEY_CODE_TO_CODE[KeyCode.Enter] = ScanCode.Enter;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export namespace KeyCodeUtils {
|
export function keyCodeToString(keyCode: KeyCode): string {
|
||||||
export function toString(keyCode: KeyCode): string {
|
return uiMap.keyCodeToStr(keyCode);
|
||||||
return uiMap.keyCodeToStr(keyCode);
|
}
|
||||||
}
|
export function keyCodeFromString(key: string): KeyCode {
|
||||||
export function fromString(key: string): KeyCode {
|
return uiMap.strToKeyCode(key);
|
||||||
return uiMap.strToKeyCode(key);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export function toUserSettingsUS(keyCode: KeyCode): string {
|
export function keyCodeToUserSettingsUS(keyCode: KeyCode): string {
|
||||||
return userSettingsUSMap.keyCodeToStr(keyCode);
|
return userSettingsUSMap.keyCodeToStr(keyCode);
|
||||||
}
|
}
|
||||||
export function toUserSettingsGeneral(keyCode: KeyCode): string {
|
export function keyCodeToUserSettingsGeneral(keyCode: KeyCode): string {
|
||||||
return userSettingsGeneralMap.keyCodeToStr(keyCode);
|
return userSettingsGeneralMap.keyCodeToStr(keyCode);
|
||||||
}
|
}
|
||||||
export function fromUserSettings(key: string): KeyCode {
|
export function keyCodeFromUserSettings(key: string): KeyCode {
|
||||||
return userSettingsUSMap.strToKeyCode(key) || userSettingsGeneralMap.strToKeyCode(key);
|
return userSettingsUSMap.strToKeyCode(key) || userSettingsGeneralMap.strToKeyCode(key);
|
||||||
}
|
|
||||||
|
|
||||||
export function toElectronAccelerator(keyCode: KeyCode): string | null {
|
|
||||||
if (keyCode >= KeyCode.Numpad0 && keyCode <= KeyCode.NumpadDivide) {
|
|
||||||
// [Electron Accelerators] Electron is able to parse numpad keys, but unfortunately it
|
|
||||||
// renders them just as regular keys in menus. For example, num0 is rendered as "0",
|
|
||||||
// numdiv is rendered as "/", numsub is rendered as "-".
|
|
||||||
//
|
|
||||||
// This can lead to incredible confusion, as it makes numpad based keybindings indistinguishable
|
|
||||||
// from keybindings based on regular keys.
|
|
||||||
//
|
|
||||||
// We therefore need to fall back to custom rendering for numpad keys.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (keyCode) {
|
|
||||||
case KeyCode.UpArrow:
|
|
||||||
return 'Up';
|
|
||||||
case KeyCode.DownArrow:
|
|
||||||
return 'Down';
|
|
||||||
case KeyCode.LeftArrow:
|
|
||||||
return 'Left';
|
|
||||||
case KeyCode.RightArrow:
|
|
||||||
return 'Right';
|
|
||||||
}
|
|
||||||
|
|
||||||
return uiMap.keyCodeToStr(keyCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum KeyMod {
|
export const enum KeyMod {
|
||||||
279
packages/engine-core/src/common/path.ts
Normal file
279
packages/engine-core/src/common/path.ts
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
import { CharCode } from './charCode';
|
||||||
|
|
||||||
|
export function normalize(path: string): string {
|
||||||
|
if (path.length === 0) {
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAbsolute = path.charCodeAt(0) === CharCode.Slash;
|
||||||
|
const trailingSeparator = path.charCodeAt(path.length - 1) === CharCode.Slash;
|
||||||
|
|
||||||
|
// Normalize the path
|
||||||
|
path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator);
|
||||||
|
|
||||||
|
if (path.length === 0) {
|
||||||
|
if (isAbsolute) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
return trailingSeparator ? './' : '.';
|
||||||
|
}
|
||||||
|
if (trailingSeparator) {
|
||||||
|
path += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAbsolute ? `/${path}` : path;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function join(...paths: string[]): string {
|
||||||
|
if (paths.length === 0) {
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
let joined;
|
||||||
|
for (let i = 0; i < paths.length; ++i) {
|
||||||
|
const arg = paths[i];
|
||||||
|
if (arg.length > 0) {
|
||||||
|
if (joined === undefined) {
|
||||||
|
joined = arg;
|
||||||
|
} else {
|
||||||
|
joined += `/${arg}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (joined === undefined) {
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
return normalize(joined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dirname(path: string): string {
|
||||||
|
if (path.length === 0) {
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
const hasRoot = path.charCodeAt(0) === CharCode.Slash;
|
||||||
|
let end = -1;
|
||||||
|
let matchedSlash = true;
|
||||||
|
for (let i = path.length - 1; i >= 1; --i) {
|
||||||
|
if (path.charCodeAt(i) === CharCode.Slash) {
|
||||||
|
if (!matchedSlash) {
|
||||||
|
end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We saw the first non-path separator
|
||||||
|
matchedSlash = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end === -1) {
|
||||||
|
return hasRoot ? '/' : '.';
|
||||||
|
}
|
||||||
|
if (hasRoot && end === 1) {
|
||||||
|
return '//';
|
||||||
|
}
|
||||||
|
return path.slice(0, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function basename(path: string, suffix?: string): string {
|
||||||
|
let start = 0;
|
||||||
|
let end = -1;
|
||||||
|
let matchedSlash = true;
|
||||||
|
let i;
|
||||||
|
|
||||||
|
if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
|
||||||
|
if (suffix === path) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let extIdx = suffix.length - 1;
|
||||||
|
let firstNonSlashEnd = -1;
|
||||||
|
for (i = path.length - 1; i >= 0; --i) {
|
||||||
|
const code = path.charCodeAt(i);
|
||||||
|
if (code === CharCode.Slash) {
|
||||||
|
// If we reached a path separator that was not part of a set of path
|
||||||
|
// separators at the end of the string, stop now
|
||||||
|
if (!matchedSlash) {
|
||||||
|
start = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (firstNonSlashEnd === -1) {
|
||||||
|
// We saw the first non-path separator, remember this index in case
|
||||||
|
// we need it if the extension ends up not matching
|
||||||
|
matchedSlash = false;
|
||||||
|
firstNonSlashEnd = i + 1;
|
||||||
|
}
|
||||||
|
if (extIdx >= 0) {
|
||||||
|
// Try to match the explicit extension
|
||||||
|
if (code === suffix.charCodeAt(extIdx)) {
|
||||||
|
if (--extIdx === -1) {
|
||||||
|
// We matched the extension, so mark this as the end of our path
|
||||||
|
// component
|
||||||
|
end = i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Extension does not match, so our result is the entire path
|
||||||
|
// component
|
||||||
|
extIdx = -1;
|
||||||
|
end = firstNonSlashEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start === end) {
|
||||||
|
end = firstNonSlashEnd;
|
||||||
|
} else if (end === -1) {
|
||||||
|
end = path.length;
|
||||||
|
}
|
||||||
|
return path.slice(start, end);
|
||||||
|
}
|
||||||
|
for (i = path.length - 1; i >= 0; --i) {
|
||||||
|
if (path.charCodeAt(i) === CharCode.Slash) {
|
||||||
|
// If we reached a path separator that was not part of a set of path
|
||||||
|
// separators at the end of the string, stop now
|
||||||
|
if (!matchedSlash) {
|
||||||
|
start = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (end === -1) {
|
||||||
|
// We saw the first non-path separator, mark this as the end of our
|
||||||
|
// path component
|
||||||
|
matchedSlash = false;
|
||||||
|
end = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end === -1) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return path.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extname(path: string): string {
|
||||||
|
let startDot = -1;
|
||||||
|
let startPart = 0;
|
||||||
|
let end = -1;
|
||||||
|
let matchedSlash = true;
|
||||||
|
// Track the state of characters (if any) we see before our first dot and
|
||||||
|
// after any path separator we find
|
||||||
|
let preDotState = 0;
|
||||||
|
for (let i = path.length - 1; i >= 0; --i) {
|
||||||
|
const code = path.charCodeAt(i);
|
||||||
|
if (code === CharCode.Slash) {
|
||||||
|
// If we reached a path separator that was not part of a set of path
|
||||||
|
// separators at the end of the string, stop now
|
||||||
|
if (!matchedSlash) {
|
||||||
|
startPart = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (end === -1) {
|
||||||
|
// We saw the first non-path separator, mark this as the end of our
|
||||||
|
// extension
|
||||||
|
matchedSlash = false;
|
||||||
|
end = i + 1;
|
||||||
|
}
|
||||||
|
if (code === CharCode.Period) {
|
||||||
|
// If this is our first dot, mark it as the start of our extension
|
||||||
|
if (startDot === -1) {
|
||||||
|
startDot = i;
|
||||||
|
} else if (preDotState !== 1) {
|
||||||
|
preDotState = 1;
|
||||||
|
}
|
||||||
|
} else if (startDot !== -1) {
|
||||||
|
// We saw a non-dot and non-path separator before our dot, so we should
|
||||||
|
// have a good chance at having a non-empty extension
|
||||||
|
preDotState = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
startDot === -1 ||
|
||||||
|
end === -1 ||
|
||||||
|
// We saw a non-dot character immediately before the dot
|
||||||
|
preDotState === 0 ||
|
||||||
|
// The (right-most) trimmed path component is exactly '..'
|
||||||
|
(preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return path.slice(startDot, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPosixPathSeparator(code: number | undefined) {
|
||||||
|
return code === CharCode.Slash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolves . and .. elements in a path with directory names
|
||||||
|
function normalizeString(
|
||||||
|
path: string,
|
||||||
|
allowAboveRoot: boolean,
|
||||||
|
separator: string,
|
||||||
|
isPathSeparator: (code?: number) => boolean,
|
||||||
|
) {
|
||||||
|
let res = '';
|
||||||
|
let lastSegmentLength = 0;
|
||||||
|
let lastSlash = -1;
|
||||||
|
let dots = 0;
|
||||||
|
let code = 0;
|
||||||
|
for (let i = 0; i <= path.length; ++i) {
|
||||||
|
if (i < path.length) {
|
||||||
|
code = path.charCodeAt(i);
|
||||||
|
} else if (isPathSeparator(code)) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
code = CharCode.Slash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPathSeparator(code)) {
|
||||||
|
if (lastSlash === i - 1 || dots === 1) {
|
||||||
|
// NOOP
|
||||||
|
} else if (dots === 2) {
|
||||||
|
if (
|
||||||
|
res.length < 2 ||
|
||||||
|
lastSegmentLength !== 2 ||
|
||||||
|
res.charCodeAt(res.length - 1) !== CharCode.Period ||
|
||||||
|
res.charCodeAt(res.length - 2) !== CharCode.Period
|
||||||
|
) {
|
||||||
|
if (res.length > 2) {
|
||||||
|
const lastSlashIndex = res.lastIndexOf(separator);
|
||||||
|
if (lastSlashIndex === -1) {
|
||||||
|
res = '';
|
||||||
|
lastSegmentLength = 0;
|
||||||
|
} else {
|
||||||
|
res = res.slice(0, lastSlashIndex);
|
||||||
|
lastSegmentLength = res.length - 1 - res.lastIndexOf(separator);
|
||||||
|
}
|
||||||
|
lastSlash = i;
|
||||||
|
dots = 0;
|
||||||
|
continue;
|
||||||
|
} else if (res.length !== 0) {
|
||||||
|
res = '';
|
||||||
|
lastSegmentLength = 0;
|
||||||
|
lastSlash = i;
|
||||||
|
dots = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allowAboveRoot) {
|
||||||
|
res += res.length > 0 ? `${separator}..` : '..';
|
||||||
|
lastSegmentLength = 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (res.length > 0) {
|
||||||
|
res += `${separator}${path.slice(lastSlash + 1, i)}`;
|
||||||
|
} else {
|
||||||
|
res = path.slice(lastSlash + 1, i);
|
||||||
|
}
|
||||||
|
lastSegmentLength = i - lastSlash - 1;
|
||||||
|
}
|
||||||
|
lastSlash = i;
|
||||||
|
dots = 0;
|
||||||
|
} else if (code === CharCode.Period && dots !== -1) {
|
||||||
|
++dots;
|
||||||
|
} else {
|
||||||
|
dots = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
7
packages/engine-core/src/common/schemas.ts
Normal file
7
packages/engine-core/src/common/schemas.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const http = 'http';
|
||||||
|
|
||||||
|
export const https = 'https';
|
||||||
|
|
||||||
|
export const file = 'file';
|
||||||
|
|
||||||
|
export const untitled = 'untitled';
|
||||||
88
packages/engine-core/src/common/strings.ts
Normal file
88
packages/engine-core/src/common/strings.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { CharCode } from './charCode';
|
||||||
|
|
||||||
|
export function compareSubstring(
|
||||||
|
a: string,
|
||||||
|
b: string,
|
||||||
|
aStart: number = 0,
|
||||||
|
aEnd: number = a.length,
|
||||||
|
bStart: number = 0,
|
||||||
|
bEnd: number = b.length,
|
||||||
|
): number {
|
||||||
|
for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) {
|
||||||
|
const codeA = a.charCodeAt(aStart);
|
||||||
|
const codeB = b.charCodeAt(bStart);
|
||||||
|
if (codeA < codeB) {
|
||||||
|
return -1;
|
||||||
|
} else if (codeA > codeB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const aLen = aEnd - aStart;
|
||||||
|
const bLen = bEnd - bStart;
|
||||||
|
if (aLen < bLen) {
|
||||||
|
return -1;
|
||||||
|
} else if (aLen > bLen) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareIgnoreCase(a: string, b: string): number {
|
||||||
|
return compareSubstringIgnoreCase(a, b, 0, a.length, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareSubstringIgnoreCase(
|
||||||
|
a: string,
|
||||||
|
b: string,
|
||||||
|
aStart: number = 0,
|
||||||
|
aEnd: number = a.length,
|
||||||
|
bStart: number = 0,
|
||||||
|
bEnd: number = b.length,
|
||||||
|
): number {
|
||||||
|
for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) {
|
||||||
|
let codeA = a.charCodeAt(aStart);
|
||||||
|
let codeB = b.charCodeAt(bStart);
|
||||||
|
|
||||||
|
if (codeA === codeB) {
|
||||||
|
// equal
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeA >= 128 || codeB >= 128) {
|
||||||
|
// not ASCII letters -> fallback to lower-casing strings
|
||||||
|
return compareSubstring(a.toLowerCase(), b.toLowerCase(), aStart, aEnd, bStart, bEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapper lower-case ascii letter onto upper-case varinats
|
||||||
|
// [97-122] (lower ascii) --> [65-90] (upper ascii)
|
||||||
|
if (isLowerAsciiLetter(codeA)) {
|
||||||
|
codeA -= 32;
|
||||||
|
}
|
||||||
|
if (isLowerAsciiLetter(codeB)) {
|
||||||
|
codeB -= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare both code points
|
||||||
|
const diff = codeA - codeB;
|
||||||
|
if (diff === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aLen = aEnd - aStart;
|
||||||
|
const bLen = bEnd - bStart;
|
||||||
|
|
||||||
|
if (aLen < bLen) {
|
||||||
|
return -1;
|
||||||
|
} else if (aLen > bLen) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLowerAsciiLetter(code: number): boolean {
|
||||||
|
return code >= CharCode.a && code <= CharCode.z;
|
||||||
|
}
|
||||||
297
packages/engine-core/src/common/ternarySearchTree.ts
Normal file
297
packages/engine-core/src/common/ternarySearchTree.ts
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
import { CharCode } from './charCode';
|
||||||
|
import { compareSubstring, compareSubstringIgnoreCase } from './strings';
|
||||||
|
|
||||||
|
export interface IKeyIterator<K> {
|
||||||
|
reset(key: K): this;
|
||||||
|
next(): this;
|
||||||
|
|
||||||
|
hasNext(): boolean;
|
||||||
|
compare(a: string): number;
|
||||||
|
value(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PathIterator implements IKeyIterator<string> {
|
||||||
|
private _value!: string;
|
||||||
|
private _valueLen!: number;
|
||||||
|
private _from!: number;
|
||||||
|
private _to!: number;
|
||||||
|
|
||||||
|
constructor(private readonly _caseSensitive: boolean = true) {}
|
||||||
|
|
||||||
|
reset(key: string): this {
|
||||||
|
this._from = 0;
|
||||||
|
this._to = 0;
|
||||||
|
this._value = key;
|
||||||
|
this._valueLen = key.length;
|
||||||
|
|
||||||
|
for (let pos = key.length - 1; pos >= 0; pos--, this._valueLen--) {
|
||||||
|
const ch = this._value.charCodeAt(pos);
|
||||||
|
if (!(ch === CharCode.Slash)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
next(): this {
|
||||||
|
this._from = this._to;
|
||||||
|
|
||||||
|
let justSeps = true;
|
||||||
|
for (; this._to < this._valueLen; this._to++) {
|
||||||
|
const ch = this._value.charCodeAt(this._to);
|
||||||
|
if (ch === CharCode.Slash) {
|
||||||
|
if (justSeps) {
|
||||||
|
this._from++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
justSeps = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext(): boolean {
|
||||||
|
return this._to < this._valueLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
compare(a: string): number {
|
||||||
|
return this._caseSensitive
|
||||||
|
? compareSubstring(a, this._value, 0, a.length, this._from, this._to)
|
||||||
|
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
|
||||||
|
}
|
||||||
|
|
||||||
|
value(): string {
|
||||||
|
return this._value.substring(this._from, this._to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TernarySearchTreeNode<K, V> {
|
||||||
|
height: number = 1;
|
||||||
|
segment!: string;
|
||||||
|
value: V | undefined;
|
||||||
|
key: K | undefined;
|
||||||
|
left: TernarySearchTreeNode<K, V> | undefined;
|
||||||
|
mid: TernarySearchTreeNode<K, V> | undefined;
|
||||||
|
right: TernarySearchTreeNode<K, V> | undefined;
|
||||||
|
|
||||||
|
isEmpty(): boolean {
|
||||||
|
return !this.left && !this.mid && !this.right && !this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotateLeft() {
|
||||||
|
const tmp = this.right!;
|
||||||
|
this.right = tmp.left;
|
||||||
|
tmp.left = this;
|
||||||
|
this.updateHeight();
|
||||||
|
tmp.updateHeight();
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotateRight() {
|
||||||
|
const tmp = this.left!;
|
||||||
|
this.left = tmp.right;
|
||||||
|
tmp.right = this;
|
||||||
|
this.updateHeight();
|
||||||
|
tmp.updateHeight();
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHeight() {
|
||||||
|
this.height = 1 + Math.max(this.heightLeft, this.heightRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
balanceFactor() {
|
||||||
|
return this.heightRight - this.heightLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
get heightLeft() {
|
||||||
|
return this.left?.height ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get heightRight() {
|
||||||
|
return this.right?.height ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum Dir {
|
||||||
|
Left = -1,
|
||||||
|
Mid = 0,
|
||||||
|
Right = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TST的应用场景包括但不限于搜索引擎、代码检查、自动补全等功能,尤其适用于大量字符串查询的场景 。
|
||||||
|
* 例如,在实现搜索框智能提示功能时,TST可以高效地进行前缀匹配,快速给出用户可能的输入选项 。
|
||||||
|
*/
|
||||||
|
export class TernarySearchTree<K, V> {
|
||||||
|
static forPaths<E>(ignorePathCasing = false): TernarySearchTree<string, E> {
|
||||||
|
return new TernarySearchTree<string, E>(new PathIterator(ignorePathCasing));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _iter: IKeyIterator<K>;
|
||||||
|
|
||||||
|
private _root: TernarySearchTreeNode<K, V> | undefined;
|
||||||
|
|
||||||
|
constructor(segments: IKeyIterator<K>) {
|
||||||
|
this._iter = segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._root = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: K, element: V): V | undefined {
|
||||||
|
const iter = this._iter.reset(key);
|
||||||
|
|
||||||
|
if (!this._root) {
|
||||||
|
this._root = new TernarySearchTreeNode<K, V>();
|
||||||
|
this._root.segment = iter.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
const stack: [Dir, TernarySearchTreeNode<K, V>][] = [];
|
||||||
|
|
||||||
|
let node: TernarySearchTreeNode<K, V> = this._root;
|
||||||
|
while (true) {
|
||||||
|
const val = iter.compare(node.segment);
|
||||||
|
|
||||||
|
if (val > 0) {
|
||||||
|
// left
|
||||||
|
if (!node.left) {
|
||||||
|
node.left = new TernarySearchTreeNode<K, V>();
|
||||||
|
node.left.segment = iter.value();
|
||||||
|
}
|
||||||
|
stack.push([Dir.Left, node]);
|
||||||
|
node = node.left;
|
||||||
|
} else if (val < 0) {
|
||||||
|
// right
|
||||||
|
if (!node.right) {
|
||||||
|
node.right = new TernarySearchTreeNode<K, V>();
|
||||||
|
node.right.segment = iter.value();
|
||||||
|
}
|
||||||
|
stack.push([Dir.Right, node]);
|
||||||
|
node = node.right;
|
||||||
|
} else if (iter.hasNext()) {
|
||||||
|
// mid
|
||||||
|
iter.next();
|
||||||
|
if (!node.mid) {
|
||||||
|
node.mid = new TernarySearchTreeNode<K, V>();
|
||||||
|
node.mid.segment = iter.value();
|
||||||
|
}
|
||||||
|
stack.push([Dir.Mid, node]);
|
||||||
|
node = node.mid;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set value
|
||||||
|
const oldElement = node.value;
|
||||||
|
node.value = element;
|
||||||
|
node.key = key;
|
||||||
|
|
||||||
|
// balance
|
||||||
|
for (let i = stack.length - 1; i >= 0; i--) {
|
||||||
|
const node = stack[i][1];
|
||||||
|
|
||||||
|
node.updateHeight();
|
||||||
|
const bf = node.balanceFactor();
|
||||||
|
|
||||||
|
if (bf < -1 || bf > 1) {
|
||||||
|
// needs rotate
|
||||||
|
const d1 = stack[i][0];
|
||||||
|
const d2 = stack[i + 1][0];
|
||||||
|
|
||||||
|
if (d1 === Dir.Right && d2 === Dir.Right) {
|
||||||
|
//right, right -> rotate left
|
||||||
|
stack[i][1] = node.rotateLeft();
|
||||||
|
} else if (d1 === Dir.Left && d2 === Dir.Left) {
|
||||||
|
// left, left -> rotate right
|
||||||
|
stack[i][1] = node.rotateRight();
|
||||||
|
} else if (d1 === Dir.Right && d2 === Dir.Left) {
|
||||||
|
// right, left -> double rotate right, left
|
||||||
|
node.right = stack[i + 1][1] = stack[i + 1][1].rotateRight();
|
||||||
|
stack[i][1] = node.rotateLeft();
|
||||||
|
} else if (d1 === Dir.Left && d2 === Dir.Right) {
|
||||||
|
// left, right -> double rotate left, right
|
||||||
|
node.left = stack[i + 1][1] = stack[i + 1][1].rotateLeft();
|
||||||
|
stack[i][1] = node.rotateRight();
|
||||||
|
} else {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch path to parent
|
||||||
|
if (i > 0) {
|
||||||
|
switch (stack[i - 1][0]) {
|
||||||
|
case Dir.Left:
|
||||||
|
stack[i - 1][1].left = stack[i][1];
|
||||||
|
break;
|
||||||
|
case Dir.Right:
|
||||||
|
stack[i - 1][1].right = stack[i][1];
|
||||||
|
break;
|
||||||
|
case Dir.Mid:
|
||||||
|
stack[i - 1][1].mid = stack[i][1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._root = stack[0][1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: K): V | undefined {
|
||||||
|
return this._getNode(key)?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getNode(key: K) {
|
||||||
|
const iter = this._iter.reset(key);
|
||||||
|
let node = this._root;
|
||||||
|
while (node) {
|
||||||
|
const val = iter.compare(node.segment);
|
||||||
|
if (val > 0) {
|
||||||
|
// left
|
||||||
|
node = node.left;
|
||||||
|
} else if (val < 0) {
|
||||||
|
// right
|
||||||
|
node = node.right;
|
||||||
|
} else if (iter.hasNext()) {
|
||||||
|
// mid
|
||||||
|
iter.next();
|
||||||
|
node = node.mid;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
findSubstr(key: K): V | undefined {
|
||||||
|
const iter = this._iter.reset(key);
|
||||||
|
|
||||||
|
let node = this._root;
|
||||||
|
let candidate: V | undefined = undefined;
|
||||||
|
while (node) {
|
||||||
|
const val = iter.compare(node.segment);
|
||||||
|
if (val > 0) {
|
||||||
|
node = node.left;
|
||||||
|
} else if (val < 0) {
|
||||||
|
node = node.right;
|
||||||
|
} else if (iter.hasNext()) {
|
||||||
|
iter.next();
|
||||||
|
|
||||||
|
candidate = node.value || candidate;
|
||||||
|
node = node.mid;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node?.value ?? candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,133 @@
|
|||||||
|
import * as Schemas from './schemas';
|
||||||
|
import { join } from './path';
|
||||||
|
|
||||||
export interface UriComponents {
|
export interface UriComponents {
|
||||||
path: string;
|
scheme?: string;
|
||||||
|
authority?: string;
|
||||||
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EMPTY = '';
|
||||||
|
const SLASH = '/';
|
||||||
|
const URI_REGEXP = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
|
||||||
|
* This class is a simple parser which creates the basic component parts
|
||||||
|
* (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
|
||||||
|
* and encoding.
|
||||||
|
*
|
||||||
|
* ```txt
|
||||||
|
* foo://example.com:8042/over/there?name=ferret#nose
|
||||||
|
* \_/ \______________/\_________/ \_________/ \__/
|
||||||
|
* | | | | |
|
||||||
|
* scheme authority path query fragment
|
||||||
|
* | _____________________|__
|
||||||
|
* / \ / \
|
||||||
|
* urn:example:animal:ferret:nose
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export class URI implements UriComponents {
|
export class URI implements UriComponents {
|
||||||
|
readonly scheme: string;
|
||||||
|
readonly authority: string;
|
||||||
readonly path: string;
|
readonly path: string;
|
||||||
|
|
||||||
constructor(path: string) {
|
constructor(scheme?: string, authority?: string, path?: string);
|
||||||
this.path = path;
|
constructor(data?: UriComponents);
|
||||||
|
constructor(data?: UriComponents | string, authority?: string, path?: string) {
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
this.scheme = data.scheme || EMPTY;
|
||||||
|
this.authority = data.authority || EMPTY;
|
||||||
|
this.path = data.path || EMPTY;
|
||||||
|
} else {
|
||||||
|
this.scheme = data || 'file';
|
||||||
|
this.authority = authority || EMPTY;
|
||||||
|
this.path = path || EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(change: { scheme?: string; path?: string }): URI {
|
||||||
|
let { scheme, path } = change;
|
||||||
|
|
||||||
|
if (scheme === undefined) {
|
||||||
|
scheme = this.scheme;
|
||||||
|
} else if (scheme === null) {
|
||||||
|
scheme = EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path === undefined) {
|
||||||
|
path = this.path;
|
||||||
|
} else if (path === null) {
|
||||||
|
path = EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new URI({ scheme, path });
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
let res = '';
|
||||||
|
const { scheme, authority, path } = this;
|
||||||
|
if (scheme) {
|
||||||
|
res += scheme;
|
||||||
|
res += ':';
|
||||||
|
}
|
||||||
|
if (authority || scheme === Schemas.file) {
|
||||||
|
res += SLASH;
|
||||||
|
res += SLASH;
|
||||||
|
}
|
||||||
|
if (authority) {
|
||||||
|
res += authority.toLowerCase();
|
||||||
|
}
|
||||||
|
if (path) {
|
||||||
|
res += path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isUri(thing: any): thing is URI {
|
||||||
|
if (thing instanceof URI) return true;
|
||||||
|
if (!thing) return false;
|
||||||
|
|
||||||
|
return typeof thing.path === 'string' && typeof thing.authority === 'string' && typeof thing.scheme === 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
static joinPath(uri: URI, ...pathSegments: string[]) {
|
||||||
|
if (!uri.path) {
|
||||||
|
throw new URIError(`cannot call joinPath on URI without path`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri.with({ path: join(uri.path, ...pathSegments) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new URI from uri components.
|
||||||
|
*
|
||||||
|
* Unless `strict` is `true` the scheme is defaults to be `file`. This function performs
|
||||||
|
* validation and should be used for untrusted uri components retrieved from storage,
|
||||||
|
* user input, command arguments etc
|
||||||
|
*/
|
||||||
|
static from(components: UriComponents): URI {
|
||||||
|
return new URI(components);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new URI from a string, e.g. `/some/path`
|
||||||
|
*
|
||||||
|
* @param value A string which represents an URI (see `URI#toString`).
|
||||||
|
*/
|
||||||
|
static parse(value: string): URI {
|
||||||
|
const match = URI_REGEXP.exec(value);
|
||||||
|
if (!match) {
|
||||||
|
return new URI();
|
||||||
|
}
|
||||||
|
return new URI(match[2] || EMPTY, match[4] || EMPTY, match[5] || EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class URIError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(`[UriError]: ${message}`);
|
||||||
|
this.name = 'URIError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
type Event,
|
Events,
|
||||||
Emitter,
|
|
||||||
type StringDictionary,
|
type StringDictionary,
|
||||||
type JSONSchemaType,
|
type JSONSchemaType,
|
||||||
jsonTypes,
|
jsonTypes,
|
||||||
IJSONSchema,
|
IJSONSchema,
|
||||||
types,
|
types,
|
||||||
|
Disposable,
|
||||||
} from '@alilc/lowcode-shared';
|
} from '@alilc/lowcode-shared';
|
||||||
import { isUndefined, isObject } from 'lodash-es';
|
import { isUndefined, isObject } from 'lodash-es';
|
||||||
import { Extensions, Registry } from '../extension/registry';
|
import { Extensions, Registry } from '../extension/registry';
|
||||||
@ -44,7 +44,7 @@ export interface IConfigurationRegistry {
|
|||||||
* Event that fires whenever a configuration has been
|
* Event that fires whenever a configuration has been
|
||||||
* registered.
|
* registered.
|
||||||
*/
|
*/
|
||||||
readonly onDidUpdateConfiguration: Event<{
|
readonly onDidUpdateConfiguration: Events.Event<{
|
||||||
properties: ReadonlySet<string>;
|
properties: ReadonlySet<string>;
|
||||||
defaultsOverrides?: boolean;
|
defaultsOverrides?: boolean;
|
||||||
}>;
|
}>;
|
||||||
@ -133,7 +133,15 @@ export const allSettings: {
|
|||||||
patternProperties: StringDictionary<IConfigurationPropertySchema>;
|
patternProperties: StringDictionary<IConfigurationPropertySchema>;
|
||||||
} = { properties: {}, patternProperties: {} };
|
} = { properties: {}, patternProperties: {} };
|
||||||
|
|
||||||
export class ConfigurationRegistryImpl implements IConfigurationRegistry {
|
export class ConfigurationRegistryImpl extends Disposable implements IConfigurationRegistry {
|
||||||
|
private _onDidUpdateConfiguration = this._addDispose(
|
||||||
|
new Events.Emitter<{
|
||||||
|
properties: ReadonlySet<string>;
|
||||||
|
defaultsOverrides?: boolean;
|
||||||
|
}>(),
|
||||||
|
);
|
||||||
|
onDidUpdateConfiguration = this._onDidUpdateConfiguration.event;
|
||||||
|
|
||||||
private registeredConfigurationDefaults: IConfigurationDefaults[] = [];
|
private registeredConfigurationDefaults: IConfigurationDefaults[] = [];
|
||||||
private readonly configurationDefaultsOverrides: Map<
|
private readonly configurationDefaultsOverrides: Map<
|
||||||
string,
|
string,
|
||||||
@ -147,12 +155,9 @@ export class ConfigurationRegistryImpl implements IConfigurationRegistry {
|
|||||||
private readonly excludedConfigurationProperties: StringDictionary<IRegisteredConfigurationPropertySchema>;
|
private readonly excludedConfigurationProperties: StringDictionary<IRegisteredConfigurationPropertySchema>;
|
||||||
private overrideIdentifiers = new Set<string>();
|
private overrideIdentifiers = new Set<string>();
|
||||||
|
|
||||||
private propertiesChangeEmitter = new Emitter<{
|
|
||||||
properties: ReadonlySet<string>;
|
|
||||||
defaultsOverrides?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
this.configurationDefaultsOverrides = new Map();
|
this.configurationDefaultsOverrides = new Map();
|
||||||
this.configurationProperties = {};
|
this.configurationProperties = {};
|
||||||
this.excludedConfigurationProperties = {};
|
this.excludedConfigurationProperties = {};
|
||||||
@ -169,7 +174,7 @@ export class ConfigurationRegistryImpl implements IConfigurationRegistry {
|
|||||||
const properties = new Set<string>();
|
const properties = new Set<string>();
|
||||||
|
|
||||||
this.doRegisterConfigurations(configurations, validate, properties);
|
this.doRegisterConfigurations(configurations, validate, properties);
|
||||||
this.propertiesChangeEmitter.emit({ properties });
|
this._onDidUpdateConfiguration.notify({ properties });
|
||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
@ -278,7 +283,7 @@ export class ConfigurationRegistryImpl implements IConfigurationRegistry {
|
|||||||
deregisterConfigurations(configurations: IConfigurationNode[]): void {
|
deregisterConfigurations(configurations: IConfigurationNode[]): void {
|
||||||
const properties = new Set<string>();
|
const properties = new Set<string>();
|
||||||
this.doDeregisterConfigurations(configurations, properties);
|
this.doDeregisterConfigurations(configurations, properties);
|
||||||
this.propertiesChangeEmitter.emit({ properties });
|
this._onDidUpdateConfiguration.notify({ properties });
|
||||||
}
|
}
|
||||||
|
|
||||||
private doDeregisterConfigurations(
|
private doDeregisterConfigurations(
|
||||||
@ -305,7 +310,7 @@ export class ConfigurationRegistryImpl implements IConfigurationRegistry {
|
|||||||
const properties = new Set<string>();
|
const properties = new Set<string>();
|
||||||
|
|
||||||
this.doRegisterDefaultConfigurations(configurationDefaults, properties);
|
this.doRegisterDefaultConfigurations(configurationDefaults, properties);
|
||||||
this.propertiesChangeEmitter.emit({ properties, defaultsOverrides: true });
|
this._onDidUpdateConfiguration.notify({ properties, defaultsOverrides: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
private doRegisterDefaultConfigurations(
|
private doRegisterDefaultConfigurations(
|
||||||
@ -476,7 +481,7 @@ export class ConfigurationRegistryImpl implements IConfigurationRegistry {
|
|||||||
const properties = new Set<string>();
|
const properties = new Set<string>();
|
||||||
this.doDeregisterDefaultConfigurations(defaultConfigurations, properties);
|
this.doDeregisterDefaultConfigurations(defaultConfigurations, properties);
|
||||||
|
|
||||||
this.propertiesChangeEmitter.emit({ properties, defaultsOverrides: true });
|
this._onDidUpdateConfiguration.notify({ properties, defaultsOverrides: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
private doDeregisterDefaultConfigurations(
|
private doDeregisterDefaultConfigurations(
|
||||||
@ -608,15 +613,6 @@ export class ConfigurationRegistryImpl implements IConfigurationRegistry {
|
|||||||
return configurationDefaultsOverrides;
|
return configurationDefaultsOverrides;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDidUpdateConfiguration(
|
|
||||||
fn: (change: {
|
|
||||||
properties: ReadonlySet<string>;
|
|
||||||
defaultsOverrides?: boolean | undefined;
|
|
||||||
}) => void,
|
|
||||||
) {
|
|
||||||
return this.propertiesChangeEmitter.on(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerJSONConfiguration(configuration: IConfigurationNode) {
|
private registerJSONConfiguration(configuration: IConfigurationNode) {
|
||||||
const register = (configuration: IConfigurationNode) => {
|
const register = (configuration: IConfigurationNode) => {
|
||||||
const properties = configuration.properties;
|
const properties = configuration.properties;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { createDecorator, Emitter, type Event, type EventListener } from '@alilc/lowcode-shared';
|
import { createDecorator, Disposable, Events } from '@alilc/lowcode-shared';
|
||||||
import {
|
import {
|
||||||
Configuration,
|
Configuration,
|
||||||
DefaultConfiguration,
|
DefaultConfiguration,
|
||||||
@ -58,19 +58,24 @@ export interface IConfigurationService {
|
|||||||
memory?: string[];
|
memory?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
onDidChangeConfiguration: Event<IConfigurationChangeEvent>;
|
onDidChangeConfiguration: Events.Event<IConfigurationChangeEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
|
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
|
||||||
|
|
||||||
export class ConfigurationService implements IConfigurationService {
|
export class ConfigurationService extends Disposable implements IConfigurationService {
|
||||||
private configuration: Configuration;
|
private configuration: Configuration;
|
||||||
private readonly defaultConfiguration: DefaultConfiguration;
|
private readonly defaultConfiguration: DefaultConfiguration;
|
||||||
private readonly userConfiguration: UserConfiguration;
|
private readonly userConfiguration: UserConfiguration;
|
||||||
|
|
||||||
private readonly didChangeEmitter = new Emitter<IConfigurationChangeEvent>();
|
private readonly _onDidChangeConfiguration = this._addDispose(
|
||||||
|
new Events.Emitter<IConfigurationChangeEvent>(),
|
||||||
|
);
|
||||||
|
onDidChangeConfiguration = this._onDidChangeConfiguration.event;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
this.defaultConfiguration = new DefaultConfiguration();
|
this.defaultConfiguration = new DefaultConfiguration();
|
||||||
this.userConfiguration = new UserConfiguration({});
|
this.userConfiguration = new UserConfiguration({});
|
||||||
this.configuration = new Configuration(
|
this.configuration = new Configuration(
|
||||||
@ -172,11 +177,7 @@ export class ConfigurationService implements IConfigurationService {
|
|||||||
{ data: previous },
|
{ data: previous },
|
||||||
this.configuration,
|
this.configuration,
|
||||||
);
|
);
|
||||||
this.didChangeEmitter.emit(event);
|
this._onDidChangeConfiguration.notify(event);
|
||||||
}
|
|
||||||
|
|
||||||
onDidChangeConfiguration(listener: EventListener<IConfigurationChangeEvent>) {
|
|
||||||
return this.didChangeEmitter.on(listener);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { type StringDictionary, Emitter, type EventListener } from '@alilc/lowcode-shared';
|
import { type StringDictionary, Disposable, Events } from '@alilc/lowcode-shared';
|
||||||
import {
|
import {
|
||||||
ConfigurationModel,
|
ConfigurationModel,
|
||||||
type IConfigurationModel,
|
type IConfigurationModel,
|
||||||
@ -8,7 +8,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ConfigurationRegistry,
|
ConfigurationRegistry,
|
||||||
type IConfigurationPropertySchema,
|
type IConfigurationPropertySchema,
|
||||||
type IConfigurationRegistry,
|
|
||||||
type IRegisteredConfigurationPropertySchema,
|
type IRegisteredConfigurationPropertySchema,
|
||||||
} from './configurationRegistry';
|
} from './configurationRegistry';
|
||||||
import { isEqual, isNil, isPlainObject, get as lodasgGet } from 'lodash-es';
|
import { isEqual, isNil, isPlainObject, get as lodasgGet } from 'lodash-es';
|
||||||
@ -23,11 +22,14 @@ export interface IConfigurationOverrides {
|
|||||||
overrideIdentifier?: string | null;
|
overrideIdentifier?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DefaultConfiguration {
|
export class DefaultConfiguration extends Disposable {
|
||||||
private emitter = new Emitter<{
|
private _onDidChangeConfiguration = this._addDispose(
|
||||||
defaults: ConfigurationModel;
|
new Events.Emitter<{
|
||||||
properties: string[];
|
defaults: ConfigurationModel;
|
||||||
}>();
|
properties: string[];
|
||||||
|
}>(),
|
||||||
|
);
|
||||||
|
onDidChangeConfiguration = this._onDidChangeConfiguration.event;
|
||||||
|
|
||||||
private _configurationModel = ConfigurationModel.createEmptyModel();
|
private _configurationModel = ConfigurationModel.createEmptyModel();
|
||||||
|
|
||||||
@ -49,15 +51,9 @@ export class DefaultConfiguration {
|
|||||||
return this.configurationModel;
|
return this.configurationModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDidChangeConfiguration(
|
|
||||||
listener: EventListener<[{ defaults: ConfigurationModel; properties: string[] }]>,
|
|
||||||
) {
|
|
||||||
return this.emitter.on(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onDidUpdateConfiguration(properties: string[]): void {
|
private onDidUpdateConfiguration(properties: string[]): void {
|
||||||
this.updateConfigurationModel(properties, ConfigurationRegistry.getConfigurationProperties());
|
this.updateConfigurationModel(properties, ConfigurationRegistry.getConfigurationProperties());
|
||||||
this.emitter.emit({ defaults: this.configurationModel, properties });
|
this._onDidChangeConfiguration.notify({ defaults: this.configurationModel, properties });
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetConfigurationModel(): void {
|
private resetConfigurationModel(): void {
|
||||||
@ -238,7 +234,7 @@ export class UserConfiguration {
|
|||||||
|
|
||||||
async loadConfiguration(): Promise<ConfigurationModel> {
|
async loadConfiguration(): Promise<ConfigurationModel> {
|
||||||
try {
|
try {
|
||||||
// const content = await this.fileService.readFile(this.userSettingsResource);
|
// 可能远程请求或者读取对应配置
|
||||||
this.parser.parse({}, this.parseOptions);
|
this.parser.parse({}, this.parseOptions);
|
||||||
return this.parser.configurationModel;
|
return this.parser.configurationModel;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
243
packages/engine-core/src/file/file.ts
Normal file
243
packages/engine-core/src/file/file.ts
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
import { type IDisposable, Events } from '@alilc/lowcode-shared';
|
||||||
|
import { URI, IRelativePattern } from '../common';
|
||||||
|
|
||||||
|
export enum FileType {
|
||||||
|
/**
|
||||||
|
* File is unknown (neither file, directory).
|
||||||
|
*/
|
||||||
|
Unknown = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File is a normal file.
|
||||||
|
*/
|
||||||
|
File = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File is a directory.
|
||||||
|
*/
|
||||||
|
Directory = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FilePermission {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File is readonly. Components like editors should not
|
||||||
|
* offer to edit the contents.
|
||||||
|
*/
|
||||||
|
Readable = 1,
|
||||||
|
|
||||||
|
Writable = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStat {
|
||||||
|
/**
|
||||||
|
* The file type.
|
||||||
|
*/
|
||||||
|
readonly type: FileType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last modification date represented as millis from unix epoch.
|
||||||
|
*/
|
||||||
|
readonly mtime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The creation date represented as millis from unix epoch.
|
||||||
|
*/
|
||||||
|
readonly ctime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file permissions.
|
||||||
|
*/
|
||||||
|
readonly permission: FilePermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBaseFileStat {
|
||||||
|
/**
|
||||||
|
* The unified resource identifier of this file or folder.
|
||||||
|
*/
|
||||||
|
readonly resource: URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name which is the last segment
|
||||||
|
* of the {{path}}.
|
||||||
|
*/
|
||||||
|
readonly name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last modification date represented as millis from unix epoch.
|
||||||
|
*
|
||||||
|
* The value may or may not be resolved as
|
||||||
|
* it is optional.
|
||||||
|
*/
|
||||||
|
readonly mtime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The creation date represented as millis from unix epoch.
|
||||||
|
*/
|
||||||
|
readonly ctime: number;
|
||||||
|
|
||||||
|
readonly permission: FilePermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file resource with meta information and resolved children if any.
|
||||||
|
*/
|
||||||
|
export interface IFileStat extends IBaseFileStat {
|
||||||
|
/**
|
||||||
|
* The resource is a file.
|
||||||
|
*/
|
||||||
|
readonly isFile: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource is a directory.
|
||||||
|
*/
|
||||||
|
readonly isDirectory: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFileOverwriteOptions {
|
||||||
|
/**
|
||||||
|
* Set to `true` to overwrite a file if it exists. Will
|
||||||
|
* throw an error otherwise if the file does exist.
|
||||||
|
*/
|
||||||
|
readonly overwrite?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFileCreateOptions {
|
||||||
|
/**
|
||||||
|
* Set to `true` to create parent directory when it does not exist. Will
|
||||||
|
* throw an error otherwise if the file does not exist.
|
||||||
|
*/
|
||||||
|
readonly recursive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFileDeleteOptions {
|
||||||
|
/**
|
||||||
|
* Set to `true` to recursively delete any children of the file. This
|
||||||
|
* only applies to folders and can lead to an error unless provided
|
||||||
|
* if the folder is not empty.
|
||||||
|
*/
|
||||||
|
readonly recursive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFileWriteOptions extends IFileCreateOptions, IFileOverwriteOptions {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies a single change in a file.
|
||||||
|
*/
|
||||||
|
export interface IFileChange {
|
||||||
|
/**
|
||||||
|
* The type of change that occurred to the file.
|
||||||
|
*/
|
||||||
|
type: FileChangeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unified resource identifier of the file that changed.
|
||||||
|
*/
|
||||||
|
readonly resource: URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possible changes that can occur to a file.
|
||||||
|
*/
|
||||||
|
export const enum FileChangeType {
|
||||||
|
UPDATED = 1 << 1,
|
||||||
|
ADDED = 1 << 2,
|
||||||
|
DELETED = 1 << 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWatchOptions {
|
||||||
|
/**
|
||||||
|
* Set to `true` to watch for changes recursively in a folder
|
||||||
|
* and all of its children.
|
||||||
|
*/
|
||||||
|
recursive: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of glob patterns or paths to exclude from watching.
|
||||||
|
* Paths can be relative or absolute and when relative are
|
||||||
|
* resolved against the watched folder. Glob patterns are
|
||||||
|
* always matched relative to the watched folder.
|
||||||
|
*/
|
||||||
|
excludes: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional set of glob patterns or paths to include for
|
||||||
|
* watching. If not provided, all paths are considered for
|
||||||
|
* events.
|
||||||
|
* Paths can be relative or absolute and when relative are
|
||||||
|
* resolved against the watched folder. Glob patterns are
|
||||||
|
* always matched relative to the watched folder.
|
||||||
|
*/
|
||||||
|
includes?: Array<string | IRelativePattern>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If provided, allows to filter the events that the watcher should consider
|
||||||
|
* for emitting. If not provided, all events are emitted.
|
||||||
|
*
|
||||||
|
* For example, to emit added and updated events, set to:
|
||||||
|
* `FileChangeType.ADDED | FileChangeType.UPDATED`.
|
||||||
|
*/
|
||||||
|
filter?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFileSystemWatcher extends IDisposable {
|
||||||
|
/**
|
||||||
|
* An event which fires on file/folder change only for changes
|
||||||
|
* that correlate to the watch request with matching correlation
|
||||||
|
* identifier.
|
||||||
|
*/
|
||||||
|
readonly onDidChange: Events.Event<FileChangesEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileChangesEvent {}
|
||||||
|
|
||||||
|
export enum FsContants {
|
||||||
|
F_OK = 1,
|
||||||
|
R_OK = 1 << 1,
|
||||||
|
W_OK = 1 << 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFileSystemProvider {
|
||||||
|
watch(resource: URI, opts: IWatchOptions): IFileSystemWatcher;
|
||||||
|
|
||||||
|
chmod(resource: URI, mode: number): Promise<void>;
|
||||||
|
access(resource: URI, mode?: number): Promise<void>;
|
||||||
|
stat(resource: URI): Promise<IFileStat>;
|
||||||
|
mkdir(resource: URI, opts: IFileWriteOptions): Promise<void>;
|
||||||
|
readdir(resource: URI): Promise<[string, FileType][]>;
|
||||||
|
delete(resource: URI, opts: IFileDeleteOptions): Promise<void>;
|
||||||
|
|
||||||
|
rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void>;
|
||||||
|
// copy(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void>;
|
||||||
|
|
||||||
|
readFile(resource: URI): Promise<string>;
|
||||||
|
writeFile(resource: URI, content: string, opts: IFileWriteOptions): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FileSystemErrorCode {
|
||||||
|
FileExists = 'EntryExists',
|
||||||
|
FileNotFound = 'EntryNotFound',
|
||||||
|
FileNotADirectory = 'EntryNotADirectory',
|
||||||
|
FileIsADirectory = 'EntryIsADirectory',
|
||||||
|
FileTooLarge = 'EntryTooLarge',
|
||||||
|
FileNotReadable = 'EntryNotReadable',
|
||||||
|
FileNotWritable = 'EntryNotWritable',
|
||||||
|
Unavailable = 'Unavailable',
|
||||||
|
Unknown = 'Unknown',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileSystemError extends Error {
|
||||||
|
static create(error: Error | string, code: FileSystemErrorCode): FileSystemError {
|
||||||
|
const providerError = new FileSystemError(error.toString(), code);
|
||||||
|
return providerError;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
message: string,
|
||||||
|
readonly code: FileSystemErrorCode,
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = code ? `${code} (FileSystemError)` : `FileSystemError`;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
packages/engine-core/src/file/fileService.ts
Normal file
62
packages/engine-core/src/file/fileService.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { createDecorator, Disposable, IDisposable, toDisposable } from '@alilc/lowcode-shared';
|
||||||
|
import { type IFileSystemProvider } from './file';
|
||||||
|
import { URI } from '../common';
|
||||||
|
|
||||||
|
export interface IFileService {
|
||||||
|
/**
|
||||||
|
* Registers a file system provider for a certain scheme.
|
||||||
|
*/
|
||||||
|
registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a file system provider for a certain scheme.
|
||||||
|
*/
|
||||||
|
getProvider(scheme: string): IFileSystemProvider | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the file service has a registered provider for the
|
||||||
|
* provided resource.
|
||||||
|
*
|
||||||
|
* Note: this does NOT account for contributed providers from
|
||||||
|
* extensions that have not been activated yet. To include those,
|
||||||
|
* consider to call `await fileService.canHandleResource(resource)`.
|
||||||
|
*/
|
||||||
|
hasProvider(resource: URI): boolean;
|
||||||
|
|
||||||
|
withProvider(resource: URI): IFileSystemProvider | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees up any resources occupied by this service.
|
||||||
|
*/
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IFileService = createDecorator<IFileService>('fileService');
|
||||||
|
|
||||||
|
export class FileService extends Disposable implements IFileService {
|
||||||
|
private readonly provider = new Map<string, IFileSystemProvider>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable {
|
||||||
|
this.provider.set(scheme, provider);
|
||||||
|
|
||||||
|
return toDisposable(() => {
|
||||||
|
this.provider.delete(scheme);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getProvider(scheme: string): IFileSystemProvider | undefined {
|
||||||
|
return this.provider.get(scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasProvider(resource: URI): boolean {
|
||||||
|
return this.provider.has(resource.scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
withProvider(resource: URI): IFileSystemProvider | undefined {
|
||||||
|
return this.provider.get(resource.scheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
346
packages/engine-core/src/file/inMemoryFileSystemProvider.ts
Normal file
346
packages/engine-core/src/file/inMemoryFileSystemProvider.ts
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
import { Events } from '@alilc/lowcode-shared';
|
||||||
|
import {
|
||||||
|
FileType,
|
||||||
|
IFileSystemProvider,
|
||||||
|
type IStat,
|
||||||
|
type IFileChange,
|
||||||
|
type IFileDeleteOptions,
|
||||||
|
type IFileSystemWatcher,
|
||||||
|
type IWatchOptions,
|
||||||
|
type IFileWriteOptions,
|
||||||
|
type IFileOverwriteOptions,
|
||||||
|
FileSystemErrorCode,
|
||||||
|
FileSystemError,
|
||||||
|
IFileStat,
|
||||||
|
FilePermission,
|
||||||
|
FileChangeType,
|
||||||
|
FsContants,
|
||||||
|
} from './file';
|
||||||
|
import { URI, basename, dirname } from '../common';
|
||||||
|
|
||||||
|
class File implements IStat {
|
||||||
|
readonly type: FileType.File;
|
||||||
|
readonly ctime: number;
|
||||||
|
mtime: number;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
data: string;
|
||||||
|
permission = FilePermission.Writable;
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.type = FileType.File;
|
||||||
|
this.ctime = Date.now();
|
||||||
|
this.mtime = Date.now();
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Directory implements IStat {
|
||||||
|
readonly type: FileType.Directory;
|
||||||
|
readonly ctime: number;
|
||||||
|
mtime: number;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
permission = FilePermission.Writable;
|
||||||
|
readonly entries: Map<string, File | Directory>;
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.type = FileType.Directory;
|
||||||
|
this.ctime = Date.now();
|
||||||
|
this.mtime = Date.now();
|
||||||
|
this.name = name;
|
||||||
|
this.entries = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
get size() {
|
||||||
|
return this.entries.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry = File | Directory;
|
||||||
|
|
||||||
|
export class InMemoryFileSystemProvider implements IFileSystemProvider {
|
||||||
|
private readonly _onDidChangeFile = new Events.Emitter<readonly IFileChange[]>();
|
||||||
|
onDidChangeFile = this._onDidChangeFile.event;
|
||||||
|
|
||||||
|
private _bufferedChanges: IFileChange[] = [];
|
||||||
|
private _fireSoonHandle: number | undefined;
|
||||||
|
|
||||||
|
private readonly _root = new Directory('');
|
||||||
|
|
||||||
|
async chmod(resource: URI, mode: number): Promise<void> {
|
||||||
|
const entry = this._lookup(resource.path, false);
|
||||||
|
|
||||||
|
if (FilePermission.Writable < mode) {
|
||||||
|
throw FileSystemError.create('Unsupported mode', FileSystemErrorCode.Unavailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.permission = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async access(resource: URI, mode: number = FsContants.F_OK): Promise<void> {
|
||||||
|
const entry = this._lookup(resource.path, false);
|
||||||
|
|
||||||
|
if (mode === FsContants.F_OK) return;
|
||||||
|
if (mode === FsContants.R_OK && entry.permission >= FilePermission.Readable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode === FsContants.W_OK && entry.permission >= FilePermission.Writable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stat(resource: URI): Promise<IFileStat> {
|
||||||
|
const file = this._lookup(resource.path, false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
resource,
|
||||||
|
name: file.name,
|
||||||
|
ctime: file.ctime,
|
||||||
|
mtime: file.mtime,
|
||||||
|
permission: file.permission,
|
||||||
|
isDirectory: file.type === FileType.Directory,
|
||||||
|
isFile: file.type === FileType.File,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(resource: URI, opts: IWatchOptions): IFileSystemWatcher {
|
||||||
|
return { resource, opts } as any as IFileSystemWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
async mkdir(resource: URI, opts: IFileWriteOptions): Promise<void> {
|
||||||
|
const base = basename(resource.path);
|
||||||
|
const dir = dirname(resource.path);
|
||||||
|
const parent = this._lookupAsDirectory(dir, true);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
if (!opts.overwrite && parent.entries.has(base)) {
|
||||||
|
throw FileSystemError.create('directory exists', FileSystemErrorCode.FileExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = new Directory(base);
|
||||||
|
entry.mtime = Date.now();
|
||||||
|
parent.entries.set(entry.name, entry);
|
||||||
|
this._fireSoon(
|
||||||
|
{ resource: resource.with({ path: dir }), type: FileChangeType.UPDATED },
|
||||||
|
{ resource, type: FileChangeType.ADDED },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (!opts.recursive) {
|
||||||
|
throw FileSystemError.create('parent directory not found', FileSystemErrorCode.FileNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._mkdirRecursive(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async readdir(resource: URI): Promise<[string, FileType][]> {
|
||||||
|
const dir = this._lookupAsDirectory(resource.path, false);
|
||||||
|
|
||||||
|
if (dir.permission < FilePermission.Readable) {
|
||||||
|
throw FileSystemError.create('Permission denied', FileSystemErrorCode.FileNotReadable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...dir.entries.entries()].map(([name, entry]) => [name, entry.type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile(resource: URI): Promise<string> {
|
||||||
|
const file = this._lookupAsFile(resource.path, false);
|
||||||
|
|
||||||
|
if (file.permission < FilePermission.Readable) {
|
||||||
|
throw FileSystemError.create('Permission denied', FileSystemErrorCode.FileNotReadable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeFile(resource: URI, content: string, opts: IFileWriteOptions): Promise<void> {
|
||||||
|
const base = basename(resource.path);
|
||||||
|
const dir = dirname(resource.path);
|
||||||
|
const dirUri = resource.with({ path: dir });
|
||||||
|
let parent = this._lookupAsDirectory(dir, true);
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
if (!opts.recursive) {
|
||||||
|
throw FileSystemError.create('file not found', FileSystemErrorCode.FileNotFound);
|
||||||
|
}
|
||||||
|
parent = await this._mkdirRecursive(dirUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = parent.entries.get(base);
|
||||||
|
if (entry instanceof Directory) {
|
||||||
|
throw FileSystemError.create('file is directory', FileSystemErrorCode.FileIsADirectory);
|
||||||
|
}
|
||||||
|
if (entry && entry.permission < FilePermission.Writable) {
|
||||||
|
throw FileSystemError.create('Permission denied', FileSystemErrorCode.FileNotWritable);
|
||||||
|
}
|
||||||
|
if (entry && !opts.overwrite) {
|
||||||
|
throw FileSystemError.create('file exists already', FileSystemErrorCode.FileExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
entry = new File(base);
|
||||||
|
parent.entries.set(base, entry);
|
||||||
|
this._fireSoon({ resource, type: FileChangeType.ADDED });
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.mtime = Date.now();
|
||||||
|
entry.data = content;
|
||||||
|
this._fireSoon({ resource, type: FileChangeType.UPDATED });
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {
|
||||||
|
const dir = dirname(resource.path);
|
||||||
|
const base = basename(resource.path);
|
||||||
|
const parent = this._lookupAsDirectory(dir, false);
|
||||||
|
|
||||||
|
if (parent.permission < FilePermission.Writable) {
|
||||||
|
throw FileSystemError.create('Permission denied', FileSystemErrorCode.FileNotWritable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.entries.has(base)) {
|
||||||
|
const entry = parent.entries.get(base)!;
|
||||||
|
|
||||||
|
if (entry.permission < FilePermission.Writable) {
|
||||||
|
throw FileSystemError.create('Permission denied', FileSystemErrorCode.FileNotWritable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry instanceof Directory) {
|
||||||
|
if (opts.recursive) {
|
||||||
|
parent.entries.delete(base);
|
||||||
|
parent.mtime = Date.now();
|
||||||
|
} else {
|
||||||
|
throw FileSystemError.create('file is directory', FileSystemErrorCode.FileIsADirectory);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parent.entries.delete(base);
|
||||||
|
parent.mtime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fireSoon(
|
||||||
|
{ resource, type: FileChangeType.DELETED },
|
||||||
|
{ resource: resource.with({ path: dir }), type: FileChangeType.UPDATED },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> {
|
||||||
|
if (from.path === to.path) return;
|
||||||
|
|
||||||
|
const entry = this._lookup(from.path, false);
|
||||||
|
|
||||||
|
if (entry.permission < FilePermission.Writable) {
|
||||||
|
throw FileSystemError.create('Permission denied', FileSystemErrorCode.FileNotWritable);
|
||||||
|
}
|
||||||
|
if (!opts.overwrite) {
|
||||||
|
throw FileSystemError.create('file exists already', FileSystemErrorCode.FileExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldParent = this._lookupAsDirectory(dirname(from.path), false);
|
||||||
|
|
||||||
|
const newParent = this._lookupAsDirectory(dirname(to.path), false);
|
||||||
|
const newName = basename(to.path);
|
||||||
|
|
||||||
|
oldParent.entries.delete(entry.name);
|
||||||
|
entry.name = newName;
|
||||||
|
newParent.entries.set(newName, entry);
|
||||||
|
|
||||||
|
this._fireSoon({ resource: to, type: FileChangeType.ADDED }, { resource: from, type: FileChangeType.DELETED });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _mkdirRecursive(target: URI): Promise<Directory> {
|
||||||
|
const dir = dirname(target.path);
|
||||||
|
const dirUri = target.with({ path: dir });
|
||||||
|
let parent = this._lookupAsDirectory(dir, false);
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
parent = await this._mkdirRecursive(dirUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.permission < FilePermission.Writable) {
|
||||||
|
throw FileSystemError.create('Permission denied', FileSystemErrorCode.FileNotWritable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const directory = new Directory(basename(target.path));
|
||||||
|
directory.mtime = Date.now();
|
||||||
|
parent.entries.set(directory.name, directory);
|
||||||
|
this._fireSoon(
|
||||||
|
{
|
||||||
|
resource: dirUri,
|
||||||
|
type: FileChangeType.UPDATED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resource: target,
|
||||||
|
type: FileChangeType.ADDED,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- lookup
|
||||||
|
|
||||||
|
private _lookup(target: string, silent: false): Entry;
|
||||||
|
private _lookup(target: string, silent: boolean): Entry | undefined;
|
||||||
|
private _lookup(target: string, silent: boolean): Entry | undefined {
|
||||||
|
const parts = target.split('/');
|
||||||
|
|
||||||
|
let entry: Entry = this._root;
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!part) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let child: Entry | undefined;
|
||||||
|
if (entry.type === FileType.Directory) {
|
||||||
|
child = entry.entries.get(part);
|
||||||
|
}
|
||||||
|
if (!child) {
|
||||||
|
if (!silent) {
|
||||||
|
throw FileSystemError.create('file not found', FileSystemErrorCode.FileNotFound);
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry = child;
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lookupAsDirectory(target: string, silent: false): Directory;
|
||||||
|
private _lookupAsDirectory(target: string, silent: boolean): Directory | undefined;
|
||||||
|
private _lookupAsDirectory(target: string, silent: boolean): Directory | undefined {
|
||||||
|
const entry = this._lookup(target, silent);
|
||||||
|
|
||||||
|
if (entry instanceof Directory) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
if (!silent) {
|
||||||
|
throw FileSystemError.create('directory not found', FileSystemErrorCode.FileNotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lookupAsFile(target: string, silent: false): File;
|
||||||
|
private _lookupAsFile(target: string, silent: boolean): File | undefined;
|
||||||
|
private _lookupAsFile(target: string, silent: boolean): File | undefined {
|
||||||
|
const entry = this._lookup(target, silent);
|
||||||
|
if (entry instanceof File) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
if (!silent) {
|
||||||
|
throw FileSystemError.create('file is a directory', FileSystemErrorCode.FileIsADirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _fireSoon(...changes: IFileChange[]): void {
|
||||||
|
this._bufferedChanges.push(...changes);
|
||||||
|
|
||||||
|
if (this._fireSoonHandle) {
|
||||||
|
clearTimeout(this._fireSoonHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fireSoonHandle = window.setTimeout(() => {
|
||||||
|
this._onDidChangeFile.notify(this._bufferedChanges);
|
||||||
|
this._bufferedChanges.length = 0;
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/engine-core/src/file/index.ts
Normal file
3
packages/engine-core/src/file/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './file';
|
||||||
|
export * from './fileService';
|
||||||
|
export * from './inMemoryFileSystemProvider';
|
||||||
@ -2,3 +2,8 @@ export * from './configuration';
|
|||||||
export * from './extension/extension';
|
export * from './extension/extension';
|
||||||
export * from './resource';
|
export * from './resource';
|
||||||
export * from './command';
|
export * from './command';
|
||||||
|
export * from './workspace';
|
||||||
|
export * from './common';
|
||||||
|
|
||||||
|
// test
|
||||||
|
export * from './main';
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { illegalArgument, KeyCode, OperatingSystem, ScanCode } from '@alilc/lowcode-shared';
|
import { illegalArgument, OperatingSystem } from '@alilc/lowcode-shared';
|
||||||
|
import { KeyCode, ScanCode } from '../common/keyCodes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binary encoding strategy:
|
* Binary encoding strategy:
|
||||||
|
|||||||
@ -1,27 +1,70 @@
|
|||||||
import { InstantiationService } from '@alilc/lowcode-shared';
|
import { InstantiationService, BeanContainer, CtorDescriptor } from '@alilc/lowcode-shared';
|
||||||
import { IWorkbenchService } from './workbench';
|
|
||||||
import { ConfigurationService, IConfigurationService } from './configuration';
|
import { ConfigurationService, IConfigurationService } from './configuration';
|
||||||
|
import { IWorkspaceService, WorkspaceService, toWorkspaceIdentifier } from './workspace';
|
||||||
|
import { IWindowService, WindowService } from './window';
|
||||||
|
import { IFileService, FileService, InMemoryFileSystemProvider } from './file';
|
||||||
|
import { URI } from './common/uri';
|
||||||
|
import * as Schemas from './common/schemas';
|
||||||
|
|
||||||
class TestMainApplication {
|
class TestMainApplication {
|
||||||
|
instantiationService: InstantiationService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
console.log('main application');
|
console.log('main application');
|
||||||
}
|
}
|
||||||
|
|
||||||
async main() {
|
async main() {
|
||||||
const workbench = instantiationService.get(IWorkbenchService);
|
await this._initServices();
|
||||||
|
|
||||||
await configurationService.initialize();
|
const [workspaceService, windowService, fileService] = this.instantiationService.invokeFunction((accessor) => [
|
||||||
workbench.initialize();
|
accessor.get(IWorkspaceService),
|
||||||
|
accessor.get(IWindowService),
|
||||||
|
accessor.get(IFileService),
|
||||||
|
]);
|
||||||
|
|
||||||
|
fileService.registerProvider(Schemas.file, new InMemoryFileSystemProvider());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uri = URI.from({ path: '/Desktop' });
|
||||||
|
|
||||||
|
await workspaceService.enterWorkspace(toWorkspaceIdentifier(uri.path));
|
||||||
|
|
||||||
|
const fileUri = URI.joinPath(uri, 'test.lc');
|
||||||
|
|
||||||
|
await windowService.open({
|
||||||
|
urisToOpen: [{ fileUri }],
|
||||||
|
openOnlyIfExists: false,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log('error', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createServices() {
|
private _createServices(): [IConfigurationService, IWorkspaceService] {
|
||||||
const instantiationService = new InstantiationService();
|
const container = new BeanContainer();
|
||||||
|
|
||||||
const configurationService = new ConfigurationService();
|
const configurationService = new ConfigurationService();
|
||||||
instantiationService.container.set(IConfigurationService, configurationService);
|
container.set(IConfigurationService, configurationService);
|
||||||
|
|
||||||
|
const workspaceService = new WorkspaceService();
|
||||||
|
container.set(IWorkspaceService, workspaceService);
|
||||||
|
|
||||||
|
container.set(IFileService, new FileService());
|
||||||
|
|
||||||
|
container.set(IWindowService, new CtorDescriptor(WindowService));
|
||||||
|
|
||||||
|
this.instantiationService = new InstantiationService(container);
|
||||||
|
|
||||||
|
return [configurationService, workspaceService];
|
||||||
}
|
}
|
||||||
|
|
||||||
initServices() {}
|
private async _initServices() {
|
||||||
|
const [configurationService, workspaceService] = this._createServices();
|
||||||
|
|
||||||
|
await configurationService.initialize();
|
||||||
|
// init workspace
|
||||||
|
await workspaceService.initialize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createLowCodeEngineApp() {
|
export async function createLowCodeEngineApp() {
|
||||||
|
|||||||
2
packages/engine-core/src/window/index.ts
Normal file
2
packages/engine-core/src/window/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './window';
|
||||||
|
export * from './windowService';
|
||||||
79
packages/engine-core/src/window/window.ts
Normal file
79
packages/engine-core/src/window/window.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { type IDisposable, type Events } from '@alilc/lowcode-shared';
|
||||||
|
import { URI } from '../common';
|
||||||
|
import { IWorkspaceIdentifier } from '../workspace';
|
||||||
|
|
||||||
|
export const enum WindowMode {
|
||||||
|
Maximized,
|
||||||
|
Normal,
|
||||||
|
Fullscreen,
|
||||||
|
Custom,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWindowState {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
mode?: WindowMode;
|
||||||
|
zoomLevel?: number;
|
||||||
|
readonly display?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultWindowState = function (mode = WindowMode.Normal): IWindowState {
|
||||||
|
return {
|
||||||
|
width: 1024,
|
||||||
|
height: 768,
|
||||||
|
mode,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IEditOptions {}
|
||||||
|
|
||||||
|
export interface IPath<T = IEditOptions> {
|
||||||
|
/**
|
||||||
|
* The file path to open within the instance
|
||||||
|
*/
|
||||||
|
readonly fileUri: URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hint that the file exists. if true, the
|
||||||
|
* file exists, if false it does not. with
|
||||||
|
* `undefined` the state is unknown.
|
||||||
|
*/
|
||||||
|
readonly exists?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional editor options to apply in the file
|
||||||
|
*/
|
||||||
|
options?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWindowConfiguration {
|
||||||
|
fileToOpenOrCreate: IPath;
|
||||||
|
|
||||||
|
workspace?: IWorkspaceIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEditWindow extends IDisposable {
|
||||||
|
readonly onWillLoad: Events.Event<void>;
|
||||||
|
readonly onDidSignalReady: Events.Event<void>;
|
||||||
|
readonly onDidDestroy: Events.Event<void>;
|
||||||
|
readonly onDidClose: Events.Event<void>;
|
||||||
|
|
||||||
|
readonly id: number;
|
||||||
|
|
||||||
|
readonly config: IWindowConfiguration | undefined;
|
||||||
|
|
||||||
|
readonly lastFocusTime: number;
|
||||||
|
focus(): void;
|
||||||
|
|
||||||
|
readonly isReady: boolean;
|
||||||
|
ready(): Promise<IEditWindow>;
|
||||||
|
|
||||||
|
load(config: IWindowConfiguration, options?: { isReload?: boolean }): void;
|
||||||
|
reload(): void;
|
||||||
|
|
||||||
|
close(): void;
|
||||||
|
|
||||||
|
sendWhenReady(channel: string, ...args: any[]): void;
|
||||||
|
}
|
||||||
104
packages/engine-core/src/window/windowImpl.ts
Normal file
104
packages/engine-core/src/window/windowImpl.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { Disposable, Events } from '@alilc/lowcode-shared';
|
||||||
|
import { IWindowState, IEditWindow, IWindowConfiguration } from './window';
|
||||||
|
import { IFileService } from '../file';
|
||||||
|
|
||||||
|
export interface IWindowCreationOptions {
|
||||||
|
readonly state: IWindowState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EditWindow extends Disposable implements IEditWindow {
|
||||||
|
private readonly _onWillLoad = this._addDispose(new Events.Emitter<void>());
|
||||||
|
onWillLoad = this._onWillLoad.event;
|
||||||
|
|
||||||
|
private readonly _onDidSignalReady = this._addDispose(new Events.Emitter<void>());
|
||||||
|
onDidSignalReady = this._onDidSignalReady.event;
|
||||||
|
|
||||||
|
private readonly _onDidClose = this._addDispose(new Events.Emitter<void>());
|
||||||
|
onDidClose = this._onDidClose.event;
|
||||||
|
|
||||||
|
private readonly _onDidDestroy = this._addDispose(new Events.Emitter<void>());
|
||||||
|
onDidDestroy = this._onDidDestroy.event;
|
||||||
|
|
||||||
|
private _id: number;
|
||||||
|
get id(): number {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _windowState: IWindowState;
|
||||||
|
|
||||||
|
private readonly _whenReadyCallbacks: ((window: IEditWindow) => void)[] = [];
|
||||||
|
|
||||||
|
private _readyState: boolean;
|
||||||
|
get isReady(): boolean {
|
||||||
|
return this._readyState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lastFocusTime;
|
||||||
|
get lastFocusTime(): number {
|
||||||
|
return this._lastFocusTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _config: IWindowConfiguration | undefined;
|
||||||
|
get config(): IWindowConfiguration | undefined {
|
||||||
|
return this._config;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: IWindowCreationOptions,
|
||||||
|
@IFileService private readonly fileService: IFileService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._windowState = options.state;
|
||||||
|
|
||||||
|
this._id = 0;
|
||||||
|
|
||||||
|
this._lastFocusTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(): Promise<IEditWindow> {
|
||||||
|
return new Promise<IEditWindow>((resolve) => {
|
||||||
|
if (this.isReady) {
|
||||||
|
return resolve(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise keep and call later when we are ready
|
||||||
|
this._whenReadyCallbacks.push(resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setReady(): void {
|
||||||
|
this._readyState = true;
|
||||||
|
|
||||||
|
// inform all waiting promises that we are ready now
|
||||||
|
while (this._whenReadyCallbacks.length) {
|
||||||
|
this._whenReadyCallbacks.pop()!(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load(config: IWindowConfiguration): void {
|
||||||
|
this._onWillLoad.notify();
|
||||||
|
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
reload(): void {}
|
||||||
|
|
||||||
|
focus(): void {}
|
||||||
|
|
||||||
|
close(): void {}
|
||||||
|
|
||||||
|
sendWhenReady(channel: string, ...args: any[]): void {
|
||||||
|
if (this.isReady) {
|
||||||
|
this.send(channel, ...args);
|
||||||
|
} else {
|
||||||
|
this.ready().then(() => {
|
||||||
|
this.send(channel, ...args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private send(channel: string, ...args: any[]): void {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
}
|
||||||
133
packages/engine-core/src/window/windowService.ts
Normal file
133
packages/engine-core/src/window/windowService.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { createDecorator, Disposable, Events, IInstantiationService } from '@alilc/lowcode-shared';
|
||||||
|
import { defaultWindowState, IEditWindow, IWindowConfiguration } from './window';
|
||||||
|
import { Schemas, URI } from '../common';
|
||||||
|
import { EditWindow } from './windowImpl';
|
||||||
|
import { IFileService } from '../file';
|
||||||
|
|
||||||
|
export interface IOpenConfiguration {
|
||||||
|
readonly urisToOpen: IWindowOpenable[];
|
||||||
|
|
||||||
|
readonly forceNewWindow?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies if the file should be only be opened
|
||||||
|
* if it exists.
|
||||||
|
*/
|
||||||
|
readonly openOnlyIfExists?: boolean;
|
||||||
|
|
||||||
|
addMode?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWindowOpenable {
|
||||||
|
label?: string;
|
||||||
|
readonly fileUri: URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWindowService {
|
||||||
|
readonly onDidOpenWindow: Events.Event<IEditWindow>;
|
||||||
|
// readonly onDidChangeFullScreen: Events.Event<{ window: IEditWindow; fullscreen: boolean }>;
|
||||||
|
// readonly onDidDestroyWindow: Events.Event<IEditWindow>;
|
||||||
|
|
||||||
|
open(openConfig: IOpenConfiguration): Promise<IEditWindow[]>;
|
||||||
|
|
||||||
|
// sendToFocused(channel: string, ...args: any[]): void;
|
||||||
|
// sendToOpeningWindow(channel: string, ...args: any[]): void;
|
||||||
|
// sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void;
|
||||||
|
|
||||||
|
getWindows(): IEditWindow[];
|
||||||
|
getWindowCount(): number;
|
||||||
|
|
||||||
|
getLastActiveWindow(): IEditWindow | undefined;
|
||||||
|
|
||||||
|
getWindowById(windowId: number): IEditWindow | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IWindowService = createDecorator<IWindowService>('windowService');
|
||||||
|
|
||||||
|
export class WindowService extends Disposable implements IWindowService {
|
||||||
|
private _onDidOpenWindow = this._addDispose(new Events.Emitter<IEditWindow>());
|
||||||
|
onDidOpenWindow = this._onDidOpenWindow.event;
|
||||||
|
|
||||||
|
private readonly windows = new Map<number, IEditWindow>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
|
@IFileService private readonly fileService: IFileService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
getWindows(): IEditWindow[] {
|
||||||
|
return [...this.windows.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
getWindowCount(): number {
|
||||||
|
return this.windows.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWindowById(windowId: number): IEditWindow | undefined {
|
||||||
|
return this.windows.get(windowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastActiveWindow(): IEditWindow | undefined {
|
||||||
|
let lastFocusedWindow: IEditWindow | undefined = undefined;
|
||||||
|
let maxLastFocusTime = Number.MIN_VALUE;
|
||||||
|
|
||||||
|
const windows = this.getWindows();
|
||||||
|
for (const window of windows) {
|
||||||
|
if (window.lastFocusTime > maxLastFocusTime) {
|
||||||
|
maxLastFocusTime = window.lastFocusTime;
|
||||||
|
lastFocusedWindow = window;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastFocusedWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
async open(openConfig: IOpenConfiguration): Promise<IEditWindow[]> {
|
||||||
|
return this._doOpen(openConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _doOpen(openConfig: IOpenConfiguration): Promise<IEditWindow[]> {
|
||||||
|
const usedWindows: IEditWindow[] = [];
|
||||||
|
const { urisToOpen, openOnlyIfExists } = openConfig;
|
||||||
|
|
||||||
|
for (const item of urisToOpen) {
|
||||||
|
const fs = this.fileService.getProvider(Schemas.file)!;
|
||||||
|
|
||||||
|
let exists = false;
|
||||||
|
try {
|
||||||
|
await fs.access(item.fileUri);
|
||||||
|
exists = true;
|
||||||
|
} catch {
|
||||||
|
if (openOnlyIfExists) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: IWindowConfiguration = {
|
||||||
|
fileToOpenOrCreate: {
|
||||||
|
fileUri: item.fileUri,
|
||||||
|
exists,
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const window = await this._openInEditWindow(config);
|
||||||
|
|
||||||
|
usedWindows.push(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
return usedWindows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _openInEditWindow(config: IWindowConfiguration): Promise<IEditWindow> {
|
||||||
|
const newWindow = this.instantiationService.createInstance(EditWindow, defaultWindowState());
|
||||||
|
|
||||||
|
this.windows.set(newWindow.id, newWindow);
|
||||||
|
|
||||||
|
// Indicate new window via event
|
||||||
|
this._onDidOpenWindow.notify(newWindow);
|
||||||
|
|
||||||
|
newWindow.load(config);
|
||||||
|
|
||||||
|
return newWindow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from './workbenchService';
|
export * from './workbenchService';
|
||||||
|
export * from './workbench';
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { type Event, type EventListener, Emitter } from '@alilc/lowcode-shared';
|
import { Disposable, Events } from '@alilc/lowcode-shared';
|
||||||
import { IWidget } from './widget';
|
import { IWidget } from './widget';
|
||||||
import { Extensions, Registry } from '../../extension/registry';
|
import { Extensions, Registry } from '../../extension/registry';
|
||||||
|
|
||||||
export interface IWidgetRegistry<View> {
|
export interface IWidgetRegistry<View> {
|
||||||
onDidRegister: Event<IWidget<View>[]>;
|
onDidRegister: Events.Event<IWidget<View>[]>;
|
||||||
|
|
||||||
registerWidget(widget: IWidget<View>): string;
|
registerWidget(widget: IWidget<View>): string;
|
||||||
|
|
||||||
@ -12,13 +12,15 @@ export interface IWidgetRegistry<View> {
|
|||||||
getWidgets(): IWidget<View>[];
|
getWidgets(): IWidget<View>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WidgetRegistryImpl<View> implements IWidgetRegistry<View> {
|
export class WidgetRegistryImpl<View> extends Disposable implements IWidgetRegistry<View> {
|
||||||
private _widgets: Map<string, IWidget<View>> = new Map();
|
private _widgets: Map<string, IWidget<View>> = new Map();
|
||||||
|
|
||||||
private emitter = new Emitter<IWidget<View>[]>();
|
private _onDidRegister = this._addDispose(new Events.Emitter<IWidget<View>[]>());
|
||||||
|
|
||||||
onDidRegister(fn: EventListener<IWidget<View>[]>) {
|
onDidRegister = this._onDidRegister.event;
|
||||||
return this.emitter.on(fn);
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getWidgets(): IWidget<View>[] {
|
getWidgets(): IWidget<View>[] {
|
||||||
|
|||||||
5
packages/engine-core/src/workbench/workbench.ts
Normal file
5
packages/engine-core/src/workbench/workbench.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const enum WorkbenchState {
|
||||||
|
EMPTY = 1,
|
||||||
|
FOLDER,
|
||||||
|
WORKSPACE /* preset */,
|
||||||
|
}
|
||||||
@ -1,140 +0,0 @@
|
|||||||
import { URI } from '../../common/uri';
|
|
||||||
|
|
||||||
export enum FileType {
|
|
||||||
/**
|
|
||||||
* File is unknown (neither file, directory).
|
|
||||||
*/
|
|
||||||
Unknown = 0,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File is a normal file.
|
|
||||||
*/
|
|
||||||
File = 1,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File is a directory.
|
|
||||||
*/
|
|
||||||
Directory = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IStat {
|
|
||||||
/**
|
|
||||||
* The file type.
|
|
||||||
*/
|
|
||||||
readonly type: FileType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The last modification date represented as millis from unix epoch.
|
|
||||||
*/
|
|
||||||
readonly mtime: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The creation date represented as millis from unix epoch.
|
|
||||||
*/
|
|
||||||
readonly ctime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IBaseFileStat {
|
|
||||||
/**
|
|
||||||
* The unified resource identifier of this file or folder.
|
|
||||||
*/
|
|
||||||
readonly resource: URI;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name which is the last segment
|
|
||||||
* of the {{path}}.
|
|
||||||
*/
|
|
||||||
readonly name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of the file.
|
|
||||||
*
|
|
||||||
* The value may or may not be resolved as
|
|
||||||
* it is optional.
|
|
||||||
*/
|
|
||||||
readonly size?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The last modification date represented as millis from unix epoch.
|
|
||||||
*
|
|
||||||
* The value may or may not be resolved as
|
|
||||||
* it is optional.
|
|
||||||
*/
|
|
||||||
readonly mtime?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The creation date represented as millis from unix epoch.
|
|
||||||
*
|
|
||||||
* The value may or may not be resolved as
|
|
||||||
* it is optional.
|
|
||||||
*/
|
|
||||||
readonly ctime?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A unique identifier that represents the
|
|
||||||
* current state of the file or directory.
|
|
||||||
*
|
|
||||||
* The value may or may not be resolved as
|
|
||||||
* it is optional.
|
|
||||||
*/
|
|
||||||
readonly etag?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File is readonly. Components like editors should not
|
|
||||||
* offer to edit the contents.
|
|
||||||
*/
|
|
||||||
readonly readonly?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File is locked. Components like editors should offer
|
|
||||||
* to edit the contents and ask the user upon saving to
|
|
||||||
* remove the lock.
|
|
||||||
*/
|
|
||||||
readonly locked?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A file resource with meta information and resolved children if any.
|
|
||||||
*/
|
|
||||||
export interface IFileStat extends IBaseFileStat {
|
|
||||||
/**
|
|
||||||
* The resource is a file.
|
|
||||||
*/
|
|
||||||
readonly isFile: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The resource is a directory.
|
|
||||||
*/
|
|
||||||
readonly isDirectory: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The children of the file stat or undefined if none.
|
|
||||||
*/
|
|
||||||
children: IFileStat[] | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFileStatWithMetadata extends Required<IFileStat> {
|
|
||||||
readonly children: IFileStatWithMetadata[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum FileOperation {
|
|
||||||
CREATE,
|
|
||||||
DELETE,
|
|
||||||
MOVE,
|
|
||||||
COPY,
|
|
||||||
WRITE,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFileOperationEvent {
|
|
||||||
readonly resource: URI;
|
|
||||||
readonly operation: FileOperation;
|
|
||||||
|
|
||||||
isOperation(operation: FileOperation.DELETE | FileOperation.WRITE): boolean;
|
|
||||||
isOperation(
|
|
||||||
operation: FileOperation.CREATE | FileOperation.MOVE | FileOperation.COPY,
|
|
||||||
): this is IFileOperationEventWithMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFileOperationEventWithMetadata extends IFileOperationEvent {
|
|
||||||
readonly target: IFileStatWithMetadata;
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* URI -> file content
|
|
||||||
* URI -> file Stat
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface IFileManagement {}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export interface IFileService {}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import { URI } from '../common/uri';
|
|
||||||
|
|
||||||
export interface IWorkspaceFolderData {
|
|
||||||
/**
|
|
||||||
* The associated URI for this workspace folder.
|
|
||||||
*/
|
|
||||||
readonly uri: URI;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of this workspace folder. Defaults to
|
|
||||||
* the basename of its [uri-path](#Uri.path)
|
|
||||||
*/
|
|
||||||
readonly name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ordinal number of this workspace folder.
|
|
||||||
*/
|
|
||||||
readonly index: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IWorkspaceFolder extends IWorkspaceFolderData {
|
|
||||||
/**
|
|
||||||
* Given workspace folder relative path, returns the resource with the absolute path.
|
|
||||||
*/
|
|
||||||
toResource: (relativePath: string) => URI;
|
|
||||||
}
|
|
||||||
3
packages/engine-core/src/workspace/index.ts
Normal file
3
packages/engine-core/src/workspace/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './workspaceService';
|
||||||
|
export * from './workspace';
|
||||||
|
export * from './workspaceFolder';
|
||||||
@ -1,82 +0,0 @@
|
|||||||
import { type Event } from '@alilc/lowcode-shared';
|
|
||||||
import { URI } from '../../common/uri';
|
|
||||||
|
|
||||||
export interface IEditWindow {
|
|
||||||
// readonly onWillLoad: Event<ILoadEvent>;
|
|
||||||
readonly onDidSignalReady: Event<void>;
|
|
||||||
readonly onDidDestroy: Event<void>;
|
|
||||||
|
|
||||||
readonly onDidClose: Event<void>;
|
|
||||||
|
|
||||||
readonly id: number;
|
|
||||||
|
|
||||||
readonly config: IWindowConfiguration | undefined;
|
|
||||||
|
|
||||||
readonly isReady: boolean;
|
|
||||||
ready(): Promise<IEditWindow>;
|
|
||||||
|
|
||||||
load(config: IWindowConfiguration, options?: { isReload?: boolean }): void;
|
|
||||||
reload(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IWindowConfiguration {
|
|
||||||
filesToOpenOrCreate?: IPath[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPath<T = any> {
|
|
||||||
/**
|
|
||||||
* Optional editor options to apply in the file
|
|
||||||
*/
|
|
||||||
readonly options?: T;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The file path to open within the instance
|
|
||||||
*/
|
|
||||||
fileUri?: URI;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies if the file should be only be opened
|
|
||||||
* if it exists.
|
|
||||||
*/
|
|
||||||
readonly openOnlyIfExists?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum WindowMode {
|
|
||||||
Maximized,
|
|
||||||
Normal,
|
|
||||||
Fullscreen,
|
|
||||||
Custom,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IWindowState {
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
mode?: WindowMode;
|
|
||||||
zoomLevel?: number;
|
|
||||||
readonly display?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IOpenConfiguration {
|
|
||||||
readonly urisToOpen?: IWindowOpenable[];
|
|
||||||
readonly preferNewWindow?: boolean;
|
|
||||||
readonly forceNewWindow?: boolean;
|
|
||||||
readonly forceNewTabbedWindow?: boolean;
|
|
||||||
readonly forceReuseWindow?: boolean;
|
|
||||||
readonly forceEmpty?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IBaseWindowOpenable {
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFolderToOpen extends IBaseWindowOpenable {
|
|
||||||
readonly folderUri: URI;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFileToOpen extends IBaseWindowOpenable {
|
|
||||||
readonly fileUri: URI;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IWindowOpenable = IFolderToOpen | IFileToOpen;
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import { type Event } from '@alilc/lowcode-shared';
|
|
||||||
import { IEditWindow, IOpenConfiguration } from './window';
|
|
||||||
|
|
||||||
export interface IWindowService {
|
|
||||||
readonly onDidOpenWindow: Event<IEditWindow>;
|
|
||||||
readonly onDidSignalReadyWindow: Event<IEditWindow>;
|
|
||||||
readonly onDidChangeFullScreen: Event<{ window: IEditWindow; fullscreen: boolean }>;
|
|
||||||
readonly onDidDestroyWindow: Event<IEditWindow>;
|
|
||||||
|
|
||||||
open(openConfig: IOpenConfiguration): Promise<IEditWindow[]>;
|
|
||||||
|
|
||||||
sendToFocused(channel: string, ...args: any[]): void;
|
|
||||||
sendToOpeningWindow(channel: string, ...args: any[]): void;
|
|
||||||
sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void;
|
|
||||||
|
|
||||||
getWindows(): IEditWindow[];
|
|
||||||
getWindowCount(): number;
|
|
||||||
|
|
||||||
getFocusedWindow(): IEditWindow | undefined;
|
|
||||||
getLastActiveWindow(): IEditWindow | undefined;
|
|
||||||
|
|
||||||
getWindowById(windowId: number): IEditWindow | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WindowService implements IWindowService {
|
|
||||||
private readonly windows = new Map<number, IEditWindow>();
|
|
||||||
|
|
||||||
getWindows(): IEditWindow[] {
|
|
||||||
return [...this.windows.values()];
|
|
||||||
}
|
|
||||||
|
|
||||||
getWindowCount(): number {
|
|
||||||
return this.windows.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFocusedWindow(): IEditWindow | undefined {
|
|
||||||
return this.getWindows().find((w) => w.focused);
|
|
||||||
}
|
|
||||||
|
|
||||||
getLastActiveWindow(): IEditWindow | undefined {
|
|
||||||
return this.getWindows().find((w) => w.lastActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +1,92 @@
|
|||||||
import { IWorkspaceFolder } from './folder';
|
import { URI, basename, TernarySearchTree } from '../common';
|
||||||
|
import { IWorkspaceFolder, WorkspaceFolder } from './workspaceFolder';
|
||||||
|
|
||||||
|
export interface IWorkspaceIdentifier {
|
||||||
|
/**
|
||||||
|
* Every workspace (multi-root, single folder or empty)
|
||||||
|
* has a unique identifier. It is not possible to open
|
||||||
|
* a workspace with the same `id` in multiple windows
|
||||||
|
*/
|
||||||
|
readonly id: string;
|
||||||
|
/**
|
||||||
|
* Folder path as `URI`.
|
||||||
|
*/
|
||||||
|
readonly uri: URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWorkspaceIdentifier(obj: unknown): obj is IWorkspaceIdentifier {
|
||||||
|
const workspaceIdentifier = obj as IWorkspaceIdentifier | undefined;
|
||||||
|
|
||||||
|
return typeof workspaceIdentifier?.id === 'string' && URI.isUri(workspaceIdentifier.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toWorkspaceIdentifier(pathOrWorkspace: IWorkspace | string): IWorkspaceIdentifier {
|
||||||
|
if (typeof pathOrWorkspace === 'string') {
|
||||||
|
return {
|
||||||
|
id: basename(pathOrWorkspace),
|
||||||
|
uri: URI.from({ path: pathOrWorkspace }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspace = pathOrWorkspace;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: workspace.id,
|
||||||
|
uri: workspace.uri,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* workspace -> one or more folders -> virtual files
|
|
||||||
* file -> editWindow
|
|
||||||
* editorView -> component tree schema
|
|
||||||
*
|
|
||||||
* project = (one or muti folders -> files) + some configs
|
|
||||||
*/
|
|
||||||
export interface IWorkspace {
|
export interface IWorkspace {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
|
|
||||||
|
readonly uri: URI;
|
||||||
/**
|
/**
|
||||||
* Folders in the workspace.
|
* Folders in the workspace.
|
||||||
*/
|
*/
|
||||||
readonly folders: IWorkspaceFolder[];
|
folders: IWorkspaceFolder[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Workspace implements IWorkspace {
|
export class Workspace implements IWorkspace {
|
||||||
private _folders: IWorkspaceFolder[] = [];
|
private _folders: WorkspaceFolder[];
|
||||||
|
private _foldersMap: TernarySearchTree<string, WorkspaceFolder>;
|
||||||
|
|
||||||
constructor(private _id: string) {}
|
constructor(
|
||||||
|
private _id: string,
|
||||||
|
private _uri: URI,
|
||||||
|
folders: WorkspaceFolder[],
|
||||||
|
private _ignorePathCasing = false,
|
||||||
|
) {
|
||||||
|
this.folders = folders;
|
||||||
|
}
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get uri() {
|
||||||
|
return this._uri;
|
||||||
|
}
|
||||||
|
|
||||||
get folders() {
|
get folders() {
|
||||||
return this._folders;
|
return this._folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set folders(folders: WorkspaceFolder[]) {
|
||||||
|
this._folders = folders;
|
||||||
|
this._updateFoldersMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFolder(resource: URI): IWorkspaceFolder | null {
|
||||||
|
if (!resource) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this._foldersMap.findSubstr(resource.path) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateFoldersMap(): void {
|
||||||
|
this._foldersMap = TernarySearchTree.forPaths<WorkspaceFolder>(this._ignorePathCasing);
|
||||||
|
for (const folder of this.folders) {
|
||||||
|
this._foldersMap.set(folder.uri.path, folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
packages/engine-core/src/workspace/workspaceFolder.ts
Normal file
62
packages/engine-core/src/workspace/workspaceFolder.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { URI, basename } from '../common';
|
||||||
|
|
||||||
|
export interface IWorkspaceFolderData {
|
||||||
|
/**
|
||||||
|
* The associated URI for this workspace folder.
|
||||||
|
*/
|
||||||
|
readonly uri: URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this workspace folder. Defaults to
|
||||||
|
* the basename of its [uri-path](#Uri.path)
|
||||||
|
*/
|
||||||
|
readonly name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ordinal number of this workspace folder.
|
||||||
|
*/
|
||||||
|
readonly index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkspaceFolder extends IWorkspaceFolderData {
|
||||||
|
/**
|
||||||
|
* Given workspace folder relative path, returns the resource with the absolute path.
|
||||||
|
*/
|
||||||
|
toResource: (relativePath: string) => URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkspaceFolder implements IWorkspaceFolder {
|
||||||
|
readonly uri: URI;
|
||||||
|
readonly name: string;
|
||||||
|
readonly index: number;
|
||||||
|
|
||||||
|
constructor(data: IWorkspaceFolderData) {
|
||||||
|
this.uri = data.uri;
|
||||||
|
this.name = data.name;
|
||||||
|
this.index = data.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
toResource(relativePath: string) {
|
||||||
|
return URI.joinPath(this.uri, relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): IWorkspaceFolderData {
|
||||||
|
return { uri: this.uri, name: this.name, index: this.index };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWorkspaceFolder(thing: unknown): thing is IWorkspaceFolder {
|
||||||
|
const candidate = thing as IWorkspaceFolder;
|
||||||
|
|
||||||
|
return !!(
|
||||||
|
candidate &&
|
||||||
|
typeof candidate === 'object' &&
|
||||||
|
URI.isUri(candidate.uri) &&
|
||||||
|
typeof candidate.name === 'string' &&
|
||||||
|
typeof candidate.toResource === 'function'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toWorkspaceFolder(resource: URI): WorkspaceFolder {
|
||||||
|
return new WorkspaceFolder({ uri: resource, index: 0, name: basename(resource.path) });
|
||||||
|
}
|
||||||
@ -1,7 +1,42 @@
|
|||||||
import { createDecorator } from '@alilc/lowcode-shared';
|
import { createDecorator, Disposable } from '@alilc/lowcode-shared';
|
||||||
|
import { Workspace, type IWorkspaceIdentifier, isWorkspaceIdentifier } from './workspace';
|
||||||
|
import { toWorkspaceFolder, IWorkspaceFolder } from './workspaceFolder';
|
||||||
|
import { URI } from '../common';
|
||||||
|
|
||||||
export interface IWorkspaceService {}
|
export interface IWorkspaceService {
|
||||||
|
initialize(): Promise<void>;
|
||||||
|
|
||||||
|
enterWorkspace(identifier: IWorkspaceIdentifier): Promise<void>;
|
||||||
|
|
||||||
|
getWorkspace(): Workspace;
|
||||||
|
|
||||||
|
getWorkspaceFolder(resource: URI): IWorkspaceFolder | null;
|
||||||
|
}
|
||||||
|
|
||||||
export const IWorkspaceService = createDecorator<IWorkspaceService>('workspaceService');
|
export const IWorkspaceService = createDecorator<IWorkspaceService>('workspaceService');
|
||||||
|
|
||||||
export class WorkspaceService implements IWorkspaceService {}
|
export class WorkspaceService extends Disposable implements IWorkspaceService {
|
||||||
|
private _workspace: Workspace;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize() {}
|
||||||
|
|
||||||
|
async enterWorkspace(identifier: IWorkspaceIdentifier) {
|
||||||
|
if (!isWorkspaceIdentifier(identifier)) {
|
||||||
|
throw new Error('Invalid workspace identifier');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._workspace = new Workspace(identifier.id, identifier.uri, [toWorkspaceFolder(identifier.uri)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getWorkspace(): Workspace {
|
||||||
|
return this._workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWorkspaceFolder(resource: URI) {
|
||||||
|
return this._workspace.getFolder(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,5 +3,5 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src", "src/common/uri.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { type StringDictionary, type ComponentTree } from '@alilc/lowcode-shared';
|
import { type StringDictionary, type ComponentTree } from '@alilc/lowcode-shared';
|
||||||
import { CodeRuntime } from '@alilc/lowcode-renderer-core';
|
import { CodeRuntime } from '@alilc/lowcode-renderer-core';
|
||||||
import { FunctionComponent, ComponentType } from 'react';
|
import { FunctionComponent, ComponentType } from 'react';
|
||||||
|
import { reactiveStateFactory } from '../app/reactiveState';
|
||||||
import {
|
import {
|
||||||
type LowCodeComponentProps,
|
type LowCodeComponentProps,
|
||||||
createComponent as createSchemaComponent,
|
createComponent as createSchemaComponent,
|
||||||
type ComponentOptions as SchemaComponentOptions,
|
type ComponentOptions as SchemaComponentOptions,
|
||||||
reactiveStateFactory,
|
} from '../runtime/createComponent';
|
||||||
} from '../runtime';
|
|
||||||
import { type ComponentsAccessor } from '../app';
|
import { type ComponentsAccessor } from '../app';
|
||||||
|
|
||||||
export interface ComponentOptions extends SchemaComponentOptions {
|
export interface ComponentOptions extends SchemaComponentOptions {
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import {
|
|||||||
type ReactRendererBoostsApi,
|
type ReactRendererBoostsApi,
|
||||||
} from './boosts';
|
} from './boosts';
|
||||||
import { createAppView } from './components/view';
|
import { createAppView } from './components/view';
|
||||||
import { ComponentOptions } from '../runtime';
|
import { type ComponentOptions } from '../runtime/createComponent';
|
||||||
|
|
||||||
import type { Project, Package } from '@alilc/lowcode-shared';
|
import type { Project, Package } from '@alilc/lowcode-shared';
|
||||||
import type {
|
import type {
|
||||||
@ -185,8 +185,6 @@ export class App extends Disposable implements IRendererApplication {
|
|||||||
await extensionHostService.registerPlugin(this.options.plugins ?? []);
|
await extensionHostService.registerPlugin(this.options.plugins ?? []);
|
||||||
|
|
||||||
await packageManagementService.loadPackages(this.options.packages ?? []);
|
await packageManagementService.loadPackages(this.options.packages ?? []);
|
||||||
|
|
||||||
lifeCycleService.setPhase(LifecyclePhase.Ready);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _createRouter() {
|
private async _createRouter() {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useAppContext } from '../context';
|
|||||||
import { OutletProps } from '../boosts';
|
import { OutletProps } from '../boosts';
|
||||||
import { useRouteLocation } from '../context';
|
import { useRouteLocation } from '../context';
|
||||||
import { createComponent } from '../../runtime/createComponent';
|
import { createComponent } from '../../runtime/createComponent';
|
||||||
import { reactiveStateFactory } from '../../runtime/reactiveState';
|
import { reactiveStateFactory } from '../reactiveState';
|
||||||
|
|
||||||
export function RouteOutlet(props: OutletProps) {
|
export function RouteOutlet(props: OutletProps) {
|
||||||
const app = useAppContext();
|
const app = useAppContext();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { AppContext } from '../context';
|
import { AppContext } from '../context';
|
||||||
import { type App } from '../app';
|
import { type App } from '../app';
|
||||||
import { getOrCreateComponent, reactiveStateFactory } from '../../runtime';
|
import { reactiveStateFactory } from '../reactiveState';
|
||||||
|
import { getOrCreateComponent } from '../../runtime/createComponent';
|
||||||
import { RouterView } from './routerView';
|
import { RouterView } from './routerView';
|
||||||
import { RouteOutlet } from './route';
|
import { RouteOutlet } from './route';
|
||||||
import { type WrapperComponent, type Outlet } from '../boosts';
|
import { type WrapperComponent, type Outlet } from '../boosts';
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import {
|
|||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { ComponentsAccessor } from '../app';
|
import { ComponentsAccessor } from '../app';
|
||||||
import { useReactiveStore } from './hooks/useReactiveStore';
|
import { useReactiveStore } from './useReactiveStore';
|
||||||
import { getOrCreateComponent, type ComponentOptions } from './createComponent';
|
import { getOrCreateComponent, type ComponentOptions } from './createComponent';
|
||||||
|
|
||||||
export type ReactComponent = ComponentType<any>;
|
export type ReactComponent = ComponentType<any>;
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
export * from './createComponent';
|
|
||||||
export * from './reactiveState';
|
|
||||||
export * from './elements';
|
|
||||||
@ -15,11 +15,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build:target": "vite build",
|
"build:target": "vite build",
|
||||||
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.mjs",
|
"build:dts": "tsc -p tsconfig.declaration.json && node ../../scripts/rollup-dts.mjs",
|
||||||
"test": "vitest",
|
"test": "vitest"
|
||||||
"test:cov": "build-scripts test --config build.test.json --jest-coverage"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alilc/lowcode-designer": "workspace:*",
|
|
||||||
"@alilc/lowcode-react-renderer": "workspace:*",
|
"@alilc/lowcode-react-renderer": "workspace:*",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
export default {
|
|
||||||
id: 'node_ockvuu8u911',
|
|
||||||
css: 'body{background-color:#f2f3f5}',
|
|
||||||
flows: [],
|
|
||||||
props: {
|
|
||||||
className: 'page_kvuu9hym',
|
|
||||||
pageStyle: {
|
|
||||||
backgroundColor: '#f2f3f5',
|
|
||||||
},
|
|
||||||
containerStyle: {},
|
|
||||||
templateVersion: '1.0.0',
|
|
||||||
},
|
|
||||||
state: {},
|
|
||||||
title: '',
|
|
||||||
methods: {
|
|
||||||
__initMethods__: {
|
|
||||||
type: 'JSExpression',
|
|
||||||
value: "function (exports, module) { \"use strict\";\n\nexports.__esModule = true;\nexports.func1 = func1;\nexports.helloPage = helloPage;\n\nfunction func1() {\n console.info('hello, this is a page function');\n}\n\nfunction helloPage() {\n // 你可以这么调用其他函数\n this.func1(); // 你可以这么调用组件的函数\n // this.$('textField_xxx').getValue();\n // 你可以这么使用「数据源面板」定义的「变量」\n // this.state.xxx\n // 你可以这么发送一个在「数据源面板」定义的「远程 API」\n // this.dataSourceMap['xxx'].load(data)\n // API 详见:https://go.alibaba-inc.com/help3/API\n} \n}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: 'node_ockvuu8u915',
|
|
||||||
props: {
|
|
||||||
fieldId: 'div_kvuu9gl1',
|
|
||||||
behavior: 'NORMAL',
|
|
||||||
__style__: {},
|
|
||||||
customClassName: '',
|
|
||||||
useFieldIdAsDomId: false,
|
|
||||||
},
|
|
||||||
title: '',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: 'node_ockvuu8u916',
|
|
||||||
props: {
|
|
||||||
content: {
|
|
||||||
use: 'zh-CN',
|
|
||||||
type: 'JSExpression',
|
|
||||||
'en-US': 'Tips content',
|
|
||||||
value: '"我是一个简单的测试页面"',
|
|
||||||
'zh-CN': '我是一个简单的测试页面',
|
|
||||||
extType: 'i18n',
|
|
||||||
},
|
|
||||||
fieldId: 'text_kvuu9gl2',
|
|
||||||
maxLine: 0,
|
|
||||||
behavior: 'NORMAL',
|
|
||||||
__style__: {},
|
|
||||||
showTitle: false,
|
|
||||||
},
|
|
||||||
title: '',
|
|
||||||
condition: true,
|
|
||||||
componentName: 'Text',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
condition: true,
|
|
||||||
componentName: 'Div',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
condition: true,
|
|
||||||
dataSource: {
|
|
||||||
list: [],
|
|
||||||
sync: true,
|
|
||||||
online: [],
|
|
||||||
offline: [],
|
|
||||||
globalConfig: {
|
|
||||||
fit: {
|
|
||||||
type: 'JSExpression',
|
|
||||||
value: "function main(){\n 'use strict';\n\nvar __compiledFunc__ = function fit(response) {\n var content = response.content !== undefined ? response.content : response;\n var error = {\n message: response.errorMsg || response.errors && response.errors[0] && response.errors[0].msg || response.content || '远程数据源请求出错,success is false'\n };\n var success = true;\n if (response.success !== undefined) {\n success = response.success;\n } else if (response.hasError !== undefined) {\n success = !response.hasError;\n }\n return {\n content: content,\n success: success,\n error: error\n };\n};\n return __compiledFunc__.apply(this, arguments);\n}",
|
|
||||||
extType: 'function',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lifeCycles: {
|
|
||||||
constructor: {
|
|
||||||
type: 'JSExpression',
|
|
||||||
value: "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}",
|
|
||||||
extType: 'function',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
componentName: 'Page',
|
|
||||||
};
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Base should be render NotFoundComponent 1`] = `
|
|
||||||
<div
|
|
||||||
className="lce-page page_kvuu9hym"
|
|
||||||
style={Object {}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
componentName="Div"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
componentName="Text"
|
|
||||||
>
|
|
||||||
Text Component Not Found
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Base should be render Text 1`] = `
|
|
||||||
<div
|
|
||||||
className="lce-page page_kvuu9hym"
|
|
||||||
style={Object {}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
componentName="Div"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
__designMode="design"
|
|
||||||
__style__={Object {}}
|
|
||||||
behavior="NORMAL"
|
|
||||||
componentId="node_ockvuu8u916"
|
|
||||||
fieldId="text_kvuu9gl2"
|
|
||||||
forwardRef={[Function]}
|
|
||||||
maxLine={0}
|
|
||||||
showTitle={false}
|
|
||||||
>
|
|
||||||
我是一个简单的测试页面
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import renderer from 'react-test-renderer';
|
|
||||||
import rendererContainer from '../../../src/renderer';
|
|
||||||
import SimulatorRendererView from '../../../src/renderer-view';
|
|
||||||
import { Text } from '../../utils/components';
|
|
||||||
|
|
||||||
describe('Base', () => {
|
|
||||||
const component = renderer.create(
|
|
||||||
<SimulatorRendererView
|
|
||||||
rendererContainer={rendererContainer}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
it('should be render NotFoundComponent', () => {
|
|
||||||
let tree = component.toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be render Text', () => {
|
|
||||||
// 更新 _componentsMap 值
|
|
||||||
(rendererContainer as any)._componentsMap.Text = Text;// = host.designer.componentsMap;
|
|
||||||
// 更新 components 列表
|
|
||||||
(rendererContainer as any).buildComponents();
|
|
||||||
|
|
||||||
expect(!!(rendererContainer.components as any).Text).toBeTruthy();
|
|
||||||
|
|
||||||
rendererContainer.rerender();
|
|
||||||
|
|
||||||
let tree = component.toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
export const Text = ({
|
|
||||||
__tag,
|
|
||||||
content,
|
|
||||||
...props
|
|
||||||
}: any) => (<div {...props}>{content}</div>);
|
|
||||||
|
|
||||||
export const Page = (props: any) => (<div>{props.children}</div>);
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
import { Box, Breadcrumb, Form, Select, Input, Button, Table, Pagination, Dialog } from '@alifd/next';
|
|
||||||
import defaultSchema from '../schema/basic';
|
|
||||||
import { Page } from './components';
|
|
||||||
|
|
||||||
class Designer {
|
|
||||||
componentsMap = {
|
|
||||||
Box,
|
|
||||||
Breadcrumb,
|
|
||||||
'Breadcrumb.Item': Breadcrumb.Item,
|
|
||||||
Form,
|
|
||||||
'Form.Item': Form.Item,
|
|
||||||
Select,
|
|
||||||
Input,
|
|
||||||
Button,
|
|
||||||
'Button.Group': Button.Group,
|
|
||||||
Table,
|
|
||||||
Pagination,
|
|
||||||
Dialog,
|
|
||||||
Page,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Host {
|
|
||||||
designer = new Designer();
|
|
||||||
|
|
||||||
connect = () => {}
|
|
||||||
|
|
||||||
autorun = (fn: Function) => {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
|
|
||||||
autoRender = true;
|
|
||||||
|
|
||||||
componentsConsumer = {
|
|
||||||
consume() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
schema = defaultSchema;
|
|
||||||
|
|
||||||
project = {
|
|
||||||
documents: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
path: '/',
|
|
||||||
fileName: '',
|
|
||||||
export: () => {
|
|
||||||
return this.schema;
|
|
||||||
},
|
|
||||||
getNode: () => {},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
get: () => ({}),
|
|
||||||
}
|
|
||||||
|
|
||||||
setInstance() {}
|
|
||||||
|
|
||||||
designMode = 'design'
|
|
||||||
|
|
||||||
get() {}
|
|
||||||
|
|
||||||
injectionConsumer = {
|
|
||||||
consume() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
i18nConsumer = {
|
|
||||||
consume() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 下列的函数或者方法是方便测试用 */
|
|
||||||
mockSchema = (schema: any) => {
|
|
||||||
this.schema = schema;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(window as any).LCSimulatorHost) {
|
|
||||||
(window as any).LCSimulatorHost = new Host();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (window as any).LCSimulatorHost;
|
|
||||||
@ -1,9 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "lib"
|
"outDir": "dist"
|
||||||
},
|
}
|
||||||
"include": [
|
|
||||||
"./src/", "../../index.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,14 +11,18 @@ import {
|
|||||||
} from '@alilc/lowcode-shared';
|
} from '@alilc/lowcode-shared';
|
||||||
import { type ICodeScope, CodeScope } from './codeScope';
|
import { type ICodeScope, CodeScope } from './codeScope';
|
||||||
import { mapValue } from './value';
|
import { mapValue } from './value';
|
||||||
import { evaluate } from './evaluate';
|
import { defaultSandbox } from './sandbox';
|
||||||
|
|
||||||
|
export interface ISandbox {
|
||||||
|
eval(code: string, scope: any): any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CodeRuntimeOptions<T extends StringDictionary = StringDictionary> {
|
export interface CodeRuntimeOptions<T extends StringDictionary = StringDictionary> {
|
||||||
initScopeValue?: Partial<T>;
|
initScopeValue?: Partial<T>;
|
||||||
|
|
||||||
parentScope?: ICodeScope;
|
parentScope?: ICodeScope;
|
||||||
|
|
||||||
evalCodeFunction?: EvalCodeFunction;
|
sandbox?: ISandbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICodeRuntime<T extends StringDictionary = StringDictionary> extends IDisposable {
|
export interface ICodeRuntime<T extends StringDictionary = StringDictionary> extends IDisposable {
|
||||||
@ -37,22 +41,20 @@ export interface ICodeRuntime<T extends StringDictionary = StringDictionary> ext
|
|||||||
|
|
||||||
export type NodeResolverHandler = (node: JSNode) => JSNode | false | undefined;
|
export type NodeResolverHandler = (node: JSNode) => JSNode | false | undefined;
|
||||||
|
|
||||||
export type EvalCodeFunction = (code: string, scope: any) => any;
|
|
||||||
|
|
||||||
export class CodeRuntime<T extends StringDictionary = StringDictionary>
|
export class CodeRuntime<T extends StringDictionary = StringDictionary>
|
||||||
extends Disposable
|
extends Disposable
|
||||||
implements ICodeRuntime<T>
|
implements ICodeRuntime<T>
|
||||||
{
|
{
|
||||||
private _codeScope: ICodeScope<T>;
|
private _codeScope: ICodeScope<T>;
|
||||||
|
|
||||||
private _evalCodeFunction: EvalCodeFunction = evaluate;
|
private _sandbox: ISandbox = defaultSandbox;
|
||||||
|
|
||||||
private _resolveHandlers: NodeResolverHandler[] = [];
|
private _resolveHandlers: NodeResolverHandler[] = [];
|
||||||
|
|
||||||
constructor(options: CodeRuntimeOptions<T> = {}) {
|
constructor(options: CodeRuntimeOptions<T> = {}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (options.evalCodeFunction) this._evalCodeFunction = options.evalCodeFunction;
|
if (options.sandbox) this._sandbox = options.sandbox;
|
||||||
this._codeScope = this._addDispose(
|
this._codeScope = this._addDispose(
|
||||||
options.parentScope
|
options.parentScope
|
||||||
? options.parentScope.createChild<T>(options.initScopeValue ?? {})
|
? options.parentScope.createChild<T>(options.initScopeValue ?? {})
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
import { type EvalCodeFunction } from './codeRuntime';
|
|
||||||
|
|
||||||
export const evaluate: EvalCodeFunction = (code: string, scope: any) => {
|
|
||||||
return new Function('scope', `"use strict";return (function(){return (${code})}).bind(scope)();`)(
|
|
||||||
scope,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
10
packages/renderer-core/src/code-runtime/sandbox.ts
Normal file
10
packages/renderer-core/src/code-runtime/sandbox.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { type ISandbox } from './codeRuntime';
|
||||||
|
|
||||||
|
export const defaultSandbox: ISandbox = {
|
||||||
|
eval(code, scope) {
|
||||||
|
return new Function(
|
||||||
|
'scope',
|
||||||
|
`"use strict";return (function(){return (${code})}).bind(scope)();`,
|
||||||
|
)(scope);
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
Disposable,
|
Disposable,
|
||||||
} from '@alilc/lowcode-shared';
|
} from '@alilc/lowcode-shared';
|
||||||
import { type ICodeRuntime } from '../code-runtime';
|
import { type ICodeRuntime } from '../code-runtime';
|
||||||
import { IWidget, Widget } from '../widget';
|
import { IWidget, Widget } from './widget';
|
||||||
|
|
||||||
export interface NormalizedComponentNode extends ComponentNode {
|
export interface NormalizedComponentNode extends ComponentNode {
|
||||||
loopArgs: [string, string];
|
loopArgs: [string, string];
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
export function illegalArgument(name?: string): Error {
|
|
||||||
if (name) {
|
|
||||||
return new Error(`Illegal argument: ${name}`);
|
|
||||||
} else {
|
|
||||||
return new Error('Illegal argument');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,9 +7,6 @@ export * from './platform';
|
|||||||
export * from './logger';
|
export * from './logger';
|
||||||
export * from './intl';
|
export * from './intl';
|
||||||
export * from './instantiation';
|
export * from './instantiation';
|
||||||
|
|
||||||
export * from './keyCodes';
|
|
||||||
export * from './errors';
|
|
||||||
export * from './disposable';
|
export * from './disposable';
|
||||||
|
|
||||||
export * from './linkedList';
|
export * from './linkedList';
|
||||||
|
|||||||
@ -52,8 +52,6 @@ export function mapDepsToBeanId(beanId: BeanIdentifier<any>, target: Constructor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBeanDependecies(
|
export function getBeanDependecies(target: Constructor): { beanId: BeanIdentifier<any>; index: number }[] {
|
||||||
target: Constructor,
|
|
||||||
): { id: BeanIdentifier<any>; index: number }[] {
|
|
||||||
return (target as any)[DEPENDENCIES] || [];
|
return (target as any)[DEPENDENCIES] || [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import { dispose, isDisposable } from '../disposable';
|
import { dispose, isDisposable } from '../disposable';
|
||||||
import { Graph, CyclicDependencyError } from '../graph';
|
import { Graph, CyclicDependencyError } from '../graph';
|
||||||
import {
|
import { type BeanIdentifier, BeanContainer, type Constructor, getBeanDependecies, CtorDescriptor } from './container';
|
||||||
type BeanIdentifier,
|
|
||||||
BeanContainer,
|
|
||||||
type Constructor,
|
|
||||||
getBeanDependecies,
|
|
||||||
CtorDescriptor,
|
|
||||||
} from './container';
|
|
||||||
import { createDecorator } from './decorators';
|
import { createDecorator } from './decorators';
|
||||||
|
|
||||||
export interface InstanceAccessor {
|
export interface InstanceAccessor {
|
||||||
@ -16,10 +10,7 @@ export interface InstanceAccessor {
|
|||||||
export interface IInstantiationService {
|
export interface IInstantiationService {
|
||||||
createInstance<T extends Constructor>(Ctor: T, ...args: any[]): InstanceType<T>;
|
createInstance<T extends Constructor>(Ctor: T, ...args: any[]): InstanceType<T>;
|
||||||
|
|
||||||
invokeFunction<R, Args extends any[] = []>(
|
invokeFunction<R, Args extends any[] = []>(fn: (accessor: InstanceAccessor, ...args: Args) => R, ...args: Args): R;
|
||||||
fn: (accessor: InstanceAccessor, ...args: Args) => R,
|
|
||||||
...args: Args
|
|
||||||
): R;
|
|
||||||
|
|
||||||
createChild(container: BeanContainer): IInstantiationService;
|
createChild(container: BeanContainer): IInstantiationService;
|
||||||
|
|
||||||
@ -77,10 +68,7 @@ export class InstantiationService implements IInstantiationService {
|
|||||||
/**
|
/**
|
||||||
* Calls a function with a service accessor.
|
* Calls a function with a service accessor.
|
||||||
*/
|
*/
|
||||||
invokeFunction<R, TS extends any[] = []>(
|
invokeFunction<R, TS extends any[] = []>(fn: (accessor: InstanceAccessor, ...args: TS) => R, ...args: TS): R {
|
||||||
fn: (accessor: InstanceAccessor, ...args: TS) => R,
|
|
||||||
...args: TS
|
|
||||||
): R {
|
|
||||||
this._throwIfDisposed();
|
this._throwIfDisposed();
|
||||||
|
|
||||||
const accessor: InstanceAccessor = {
|
const accessor: InstanceAccessor = {
|
||||||
@ -105,9 +93,9 @@ export class InstantiationService implements IInstantiationService {
|
|||||||
const beanArgs = [];
|
const beanArgs = [];
|
||||||
|
|
||||||
for (const dependency of beanDependencies) {
|
for (const dependency of beanDependencies) {
|
||||||
const instance = this._getOrCreateInstance(dependency.id);
|
const instance = this._getOrCreateInstance(dependency.beanId);
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
throw new Error(`[createInstance] ${Ctor.name} depends on UNKNOWN bean ${dependency.id}.`);
|
throw new Error(`[createInstance] ${Ctor.name} depends on UNKNOWN bean ${dependency.beanId}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
beanArgs.push(instance);
|
beanArgs.push(instance);
|
||||||
@ -149,9 +137,7 @@ export class InstantiationService implements IInstantiationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _createAndCacheServiceInstance<T>(id: BeanIdentifier<T>, desc: CtorDescriptor<T>): T {
|
private _createAndCacheServiceInstance<T>(id: BeanIdentifier<T>, desc: CtorDescriptor<T>): T {
|
||||||
const graph = new Graph<{ id: BeanIdentifier<T>; desc: CtorDescriptor<T> }>((data) =>
|
const graph = new Graph<{ id: BeanIdentifier<T>; desc: CtorDescriptor<T> }>((data) => data.id.toString());
|
||||||
data.id.toString(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let cycleCount = 0;
|
let cycleCount = 0;
|
||||||
const stack = [{ id, desc }];
|
const stack = [{ id, desc }];
|
||||||
@ -174,16 +160,14 @@ export class InstantiationService implements IInstantiationService {
|
|||||||
|
|
||||||
// check all dependencies for existence and if they need to be created first
|
// check all dependencies for existence and if they need to be created first
|
||||||
for (const dependency of getBeanDependecies(item.desc.ctor)) {
|
for (const dependency of getBeanDependecies(item.desc.ctor)) {
|
||||||
const instanceOrDesc = this._container.get(dependency.id);
|
const instanceOrDesc = this._container.get(dependency.beanId);
|
||||||
if (!instanceOrDesc) {
|
if (!instanceOrDesc) {
|
||||||
throw new Error(
|
throw new Error(`[createInstance] ${id} depends on ${dependency.beanId} which is NOT registered.`);
|
||||||
`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instanceOrDesc instanceof CtorDescriptor) {
|
if (instanceOrDesc instanceof CtorDescriptor) {
|
||||||
const d = {
|
const d = {
|
||||||
id: dependency.id,
|
id: dependency.beanId,
|
||||||
desc: instanceOrDesc,
|
desc: instanceOrDesc,
|
||||||
};
|
};
|
||||||
graph.insertEdge(item, d);
|
graph.insertEdge(item, d);
|
||||||
@ -210,11 +194,7 @@ export class InstantiationService implements IInstantiationService {
|
|||||||
const instanceOrDesc = this._container.get(data.id);
|
const instanceOrDesc = this._container.get(data.id);
|
||||||
if (instanceOrDesc instanceof CtorDescriptor) {
|
if (instanceOrDesc instanceof CtorDescriptor) {
|
||||||
// create instance and overwrite the service collections
|
// create instance and overwrite the service collections
|
||||||
const instance = this._createServiceInstanceWithOwner(
|
const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments);
|
||||||
data.id,
|
|
||||||
data.desc.ctor,
|
|
||||||
data.desc.staticArguments,
|
|
||||||
);
|
|
||||||
this._setCreatedServiceInstance(data.id, instance);
|
this._setCreatedServiceInstance(data.id, instance);
|
||||||
}
|
}
|
||||||
graph.removeNode(data);
|
graph.removeNode(data);
|
||||||
|
|||||||
@ -3,3 +3,11 @@ export function invariant(check: unknown, message: string, thing?: any): asserts
|
|||||||
throw new Error(`Invariant failed: ${message}${thing ? ` in '${thing}'` : ''}`);
|
throw new Error(`Invariant failed: ${message}${thing ? ` in '${thing}'` : ''}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function illegalArgument(name?: string): Error {
|
||||||
|
if (name) {
|
||||||
|
return new Error(`Illegal argument: ${name}`);
|
||||||
|
} else {
|
||||||
|
return new Error('Illegal argument');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ async function run() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (buildTypes) {
|
if (buildTypes) {
|
||||||
await execa('pnpm', ['--filter', finalName[0], 'build:dts'], {
|
await execa('pnpm', ['--filter', manifest.name, 'build:dts'], {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user