├── .gitignore ├── LEGAL.md ├── LICENSE ├── README.md ├── assets └── compile.zip ├── code ├── index.tsx ├── startup.module.ts └── web-scm.plugin.ts ├── collaboration ├── index.tsx ├── plugin.ts └── server.js ├── common ├── code-blame.plugin.ts ├── code-scaning.plugin.ts ├── local-extension.module.ts ├── plugin.ts ├── shims │ └── react-dom-client.js └── zipPlugin.ts ├── components ├── ComponentMenuGroupDivider.tsx └── zipButton.tsx ├── editor.tsx ├── extensions ├── collaboration │ ├── .gitignore │ ├── .sumiignore │ ├── .vscodeignore │ ├── LEGAL.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Live.ts │ │ ├── OTClient.ts │ │ ├── UsersManager.ts │ │ ├── blocker.ts │ │ ├── decorators.ts │ │ ├── event-to-transform.ts │ │ ├── extension.ts │ │ ├── ot │ │ │ ├── README.md │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ └── text-operation.ts │ │ ├── preference.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── tsconfig.json │ ├── webpack.config.js │ └── yarn.lock └── worker-example │ ├── browser.js │ ├── package.json │ ├── worker-example.d.ts │ ├── worker-example.js │ └── worker.js ├── filesystem.tsx ├── index.html ├── main.tsx ├── merge-editor ├── index.tsx ├── merge-editor.module.ts ├── mock │ └── conflicts.mock.ts └── util.ts ├── modules ├── registerMenu.ts ├── registerZipMenu.tsx └── unregisterKeybinding.ts ├── package.json ├── plugin.ts ├── public-extensions ├── alex-ext-public.anycode-c-sharp.js ├── alex-ext-public.anycode-cpp.js ├── alex-ext-public.anycode-go.js ├── alex-ext-public.anycode-java.js ├── alex-ext-public.anycode-php.js ├── alex-ext-public.anycode-python.js ├── alex-ext-public.anycode-rust.js ├── alex-ext-public.anycode-typescript.js ├── alex-ext-public.anycode.js ├── alex-ext-public.code-runner-for-web.js ├── alex-ext-public.codeswing.js ├── alex-ext-public.css-language-features-worker.js ├── alex-ext-public.emmet.js ├── alex-ext-public.git-graph.js ├── alex-ext-public.gitlens.js ├── alex-ext-public.html-language-features-worker.js ├── alex-ext-public.image-preview.js ├── alex-ext-public.json-language-features-worker.js ├── alex-ext-public.markdown-language-features-worker.js ├── alex-ext-public.references-view.js ├── alex-ext-public.typescript-language-features-worker.js └── alex-ext-public.web-scm.js ├── search.tsx ├── startup-bundle.tsx ├── startup.tsx ├── tsconfig.json ├── webpack.config.js ├── yarn.lock └── zip.tsx /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | extensions/**/out 3 | .env -------------------------------------------------------------------------------- /LEGAL.md: -------------------------------------------------------------------------------- 1 | Legal Disclaimer 2 | 3 | Within this source code, the comments in Chinese shall be the original, governing version. Any comment in other languages are for reference only. In the event of any conflict between the Chinese language version comments and other language version comments, the Chinese language version shall prevail. 4 | 5 | 法律免责声明 6 | 7 | 关于代码注释部分,中文注释为官方版本,其它语言注释仅做参考。中文注释可能与其它语言注释存在不一致,当中文注释与其它语言注释存在不一致时,请以中文注释为准。 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-present Ant Group Co. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codeblitz-sample 示例仓库 2 | 3 | ## Codeblitz 文档地址 [官网文档](https://codeblitz.cloud.alipay.com/zh) 4 | 5 | ## 快速开始 6 | 7 | ```bash 8 | # IDE 示例 9 | npm run start 10 | 11 | # 查看 filesystem 示例 12 | npm run fs 13 | 14 | # 查看 对接代码平台 示例 15 | npm run code 16 | 17 | # 查看 基础editor 示例 18 | npm run editor 19 | 20 | # 查看协同编辑示例 21 | npx codeblitz ext link ./extensions/collaboration # 插件本地联调 22 | 23 | cd extensions/collaboration && yarn install && yarn run dev # 插件编译 24 | 25 | npm run collaboration 26 | ``` 27 | -------------------------------------------------------------------------------- /assets/compile.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensumi/codeblitz-sample/03c7dee98940b38083d4c8e945adea08464d0081/assets/compile.zip -------------------------------------------------------------------------------- /code/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { IAppInstance, AppRenderer, getDefaultLayoutConfig, SlotLocation } from '@codeblitzjs/ide-core'; 4 | import '@codeblitzjs/ide-core/languages'; 5 | import { CodeServiceModule } from '@codeblitzjs/ide-code-service'; 6 | import { CodeAPIModule, CodePlatform } from '@codeblitzjs/ide-code-api'; 7 | import { isFilesystemReady } from '@codeblitzjs/ide-sumi-core'; 8 | import { StartupModule } from './startup.module'; 9 | 10 | // 内置插件 11 | import css from '@codeblitzjs/ide-core/extensions/codeblitz.css-language-features-worker'; 12 | import html from '@codeblitzjs/ide-core/extensions/codeblitz.html-language-features-worker'; 13 | import json from '@codeblitzjs/ide-core/extensions/codeblitz.json-language-features-worker'; 14 | import markdown from '@codeblitzjs/ide-core/extensions/codeblitz.markdown-language-features-worker'; 15 | import typescript from '@codeblitzjs/ide-core/extensions/codeblitz.typescript-language-features-worker'; 16 | import gitlens from '@codeblitzjs/ide-core/extensions/codeblitz.gitlens'; 17 | import graph from '@codeblitzjs/ide-core/extensions/codeblitz.git-graph'; 18 | import imagePreview from '@codeblitzjs/ide-core/extensions/codeblitz.image-preview'; 19 | import webSCM from '@codeblitzjs/ide-core/extensions/codeblitz.web-scm'; 20 | import anycode from '@codeblitzjs/ide-core/extensions/codeblitz.anycode'; 21 | import anycodeCSharp from '@codeblitzjs/ide-core/extensions/codeblitz.anycode-c-sharp'; 22 | import anycodeCpp from '@codeblitzjs/ide-core/extensions/codeblitz.anycode-cpp'; 23 | import anycodeGo from '@codeblitzjs/ide-core/extensions/codeblitz.anycode-go'; 24 | import anycodeJava from '@codeblitzjs/ide-core/extensions/codeblitz.anycode-java'; 25 | import anycodePhp from '@codeblitzjs/ide-core/extensions/codeblitz.anycode-php'; 26 | import anycodePython from '@codeblitzjs/ide-core/extensions/codeblitz.anycode-python'; 27 | import anycodeRust from '@codeblitzjs/ide-core/extensions/codeblitz.anycode-rust'; 28 | import anycodeTypescript from '@codeblitzjs/ide-core/extensions/codeblitz.anycode-typescript'; 29 | import referencesView from '@codeblitzjs/ide-core/extensions/codeblitz.references-view'; 30 | import emmet from '@codeblitzjs/ide-core/extensions/codeblitz.emmet'; 31 | import codeswing from '@codeblitzjs/ide-core/extensions/codeblitz.codeswing'; 32 | import codeRunner from '@codeblitzjs/ide-core/extensions/codeblitz.code-runner-for-web'; 33 | import mergeConflict from '@codeblitzjs/ide-core/extensions/codeblitz.merge-conflict'; 34 | 35 | // import * as SCMPlugin from './web-scm.plugin'; 36 | import { WorkbenchEditorService } from '@opensumi/ide-editor'; 37 | 38 | 39 | /* TODO 40 | * 1. 搜索内容功能 41 | * 2. scm插件 依赖提交接口以及所有文件接口 42 | * 3. gitlens 插件依赖 blame接口 43 | */ 44 | 45 | // 访问地址 46 | 47 | 48 | isFilesystemReady().then(() => { 49 | console.log('filesystem ready'); 50 | }); 51 | 52 | const platformConfig = { 53 | github: { 54 | owner: 'opensumi', 55 | name: 'codeblitz' 56 | } 57 | }; 58 | 59 | const layoutConfig = getDefaultLayoutConfig(); 60 | layoutConfig[SlotLocation.left].modules.push( 61 | '@opensumi/ide-extension-manager', 62 | '@opensumi/ide-scm' 63 | ); 64 | 65 | let pathParts = location.pathname.split('/').filter(Boolean); 66 | 67 | const platform: any = pathParts[0] in platformConfig ? pathParts[0] : 'github'; 68 | 69 | const config = platformConfig[platform]; 70 | if (pathParts[1]) { 71 | config.owner = pathParts[1]; 72 | } 73 | if (pathParts[2]) { 74 | config.name = pathParts[2]; 75 | } 76 | config.refPath = pathParts.slice(3).join('/'); 77 | 78 | const extensionMetadata = [ 79 | css, 80 | html, 81 | json, 82 | markdown, 83 | typescript, 84 | gitlens, 85 | graph, 86 | imagePreview, 87 | webSCM, 88 | referencesView, 89 | codeswing, 90 | emmet, 91 | anycodeCSharp, 92 | anycodeCpp, 93 | anycodeGo, 94 | anycodeJava, 95 | anycodePhp, 96 | anycodePython, 97 | anycodeRust, 98 | anycodeTypescript, 99 | anycode, 100 | codeRunner, 101 | mergeConflict 102 | ] 103 | 104 | 105 | const App = () => ( 106 | { 108 | window.app = app; 109 | }} 110 | appConfig={{ 111 | // plugins: [Plugin, SCMPlugin], 112 | modules: [ 113 | CodeServiceModule.Config({ 114 | platform, 115 | owner: config.owner, 116 | name: config.name, 117 | refPath: config.refPath, 118 | commit: config.commit, 119 | hash: location.hash, 120 | gitlink: { 121 | endpoint: '/code-service', 122 | } 123 | }), 124 | CodeAPIModule, 125 | StartupModule, 126 | ], 127 | extensionMetadata, 128 | workspaceDir: `${platform}/${config.owner}/${config.name}`, 129 | layoutConfig, 130 | defaultPreferences: { 131 | 'general.theme': 'opensumi-design-dark-theme', 132 | }, 133 | }} 134 | runtimeConfig={{ 135 | scmFileTree: true, 136 | scenario: 'CODEBLITZ_DEMO' 137 | }} 138 | /> 139 | ); 140 | 141 | let key = 0; 142 | const render = () => ReactDOM.createRoot(document.getElementById('main')!).render(); 143 | render(); 144 | // for dispose test 145 | window.reset = (destroy = false) => 146 | destroy ? ReactDOM.createRoot(document.getElementById('main')!).render(
destroyed
) : render(); 147 | 148 | declare global { 149 | interface Window { 150 | app: IAppInstance; 151 | reset(destroyed?: boolean): void; 152 | testPlugin(): void; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /code/startup.module.ts: -------------------------------------------------------------------------------- 1 | import { Provider, Injectable, Autowired } from '@opensumi/di'; 2 | import { 3 | Domain, 4 | BrowserModule, 5 | CommandContribution, 6 | CommandRegistry, 7 | getLanguageId, 8 | Disposable, 9 | CommandService, 10 | } from '@opensumi/ide-core-browser'; 11 | import { CodeModelService } from '@codeblitzjs/ide-code-service'; 12 | 13 | @Domain(CommandContribution) 14 | export class AlexAppContribution extends Disposable implements CommandContribution { 15 | @Autowired() 16 | codeModel: CodeModelService; 17 | 18 | @Autowired(CommandService) 19 | commandService: CommandService; 20 | 21 | private ticket: number = 0; 22 | private tickets: number[] = []; 23 | private ignoreHash: string | null = null; 24 | 25 | registerCommands(commands: CommandRegistry): void { 26 | this.addDispose([ 27 | commands.registerCommand( 28 | { id: 'alex.env.language' }, 29 | { 30 | execute: () => getLanguageId(), 31 | } 32 | ), 33 | 34 | commands.registerCommand( 35 | { id: 'alex.codeServiceProject' }, 36 | { 37 | execute: () => { 38 | const { rootRepository } = this.codeModel; 39 | return { 40 | platform: rootRepository.platform, 41 | project: `${rootRepository.owner}/${rootRepository.name}`, 42 | projectId: `${rootRepository.owner}%2F${rootRepository.name}`, 43 | commit: rootRepository.commit, 44 | }; 45 | }, 46 | } 47 | ), 48 | 49 | commands.registerCommand( 50 | { id: 'alex.subscribe' }, 51 | { 52 | execute: () => { 53 | this.ticket++; 54 | this.tickets.push(this.ticket); 55 | return this.ticket; 56 | }, 57 | } 58 | ), 59 | 60 | commands.registerCommand( 61 | { id: 'code-service.replace-browser-url' }, 62 | { 63 | execute: (path: string) => { 64 | const fullPath = `/${this.codeModel.rootRepository.platform}${path}`; 65 | window.history.replaceState(null, '', fullPath); 66 | }, 67 | } 68 | ), 69 | 70 | commands.registerCommand( 71 | { id: 'code-service.replace-browser-url-hash' }, 72 | { 73 | execute: (hash: string) => { 74 | if (hash !== location.hash) { 75 | this.ignoreHash = hash; 76 | const { href } = window.location; 77 | const hashIndex = href.indexOf('#'); 78 | const url = hashIndex === -1 ? href : href.slice(0, hashIndex); 79 | window.location.replace(`${url}${hash}`); 80 | } 81 | }, 82 | } 83 | ), 84 | ]); 85 | 86 | const handleHashChange = () => { 87 | const { hash } = location; 88 | if (this.ignoreHash === hash) return; 89 | this.ignoreHash = null; 90 | this.commandService.executeCommand('code-service.set-line-hash', hash); 91 | }; 92 | window.addEventListener('hashchange', handleHashChange); 93 | this.addDispose({ 94 | dispose: () => { 95 | window.removeEventListener('hashchange', handleHashChange); 96 | }, 97 | }); 98 | } 99 | } 100 | 101 | @Injectable() 102 | export class StartupModule extends BrowserModule { 103 | providers: Provider[] = [AlexAppContribution]; 104 | } 105 | -------------------------------------------------------------------------------- /code/web-scm.plugin.ts: -------------------------------------------------------------------------------- 1 | import type { IPluginAPI } from '@codeblitzjs/ide-core/lib/editor'; 2 | import * as localforage from 'localforage'; 3 | import type { Uri } from '@codeblitzjs/ide-core'; 4 | import { Deferred } from '@codeblitzjs/ide-core/lib/modules/opensumi__ide-core-browser'; 5 | 6 | export const PLUGIN_ID = 'web-scm'; 7 | 8 | export interface CacheFile { 9 | uri: Uri; 10 | content: string; 11 | length?: number; 12 | type?: Status; 13 | } 14 | export enum Status { 15 | INDEX_MODIFIED, 16 | INDEX_ADDED, 17 | INDEX_DELETED, 18 | INDEX_RENAMED, 19 | INDEX_COPIED, 20 | 21 | MODIFIED, 22 | DELETED, 23 | UNTRACKED, 24 | IGNORED, 25 | INTENT_TO_ADD, 26 | ADDED, 27 | 28 | ADDED_BY_US, 29 | ADDED_BY_THEM, 30 | DELETED_BY_US, 31 | DELETED_BY_THEM, 32 | BOTH_ADDED, 33 | BOTH_DELETED, 34 | BOTH_MODIFIED, 35 | } 36 | 37 | export const activate = ({ commands }: IPluginAPI) => { 38 | if (!localforage.supports(localforage.INDEXEDDB)) { 39 | throw new Error('[SCM] IndexedDB Not Support'); 40 | } 41 | const workerReady = new Deferred(); 42 | 43 | // 只存储在IndexedDB 44 | localforage.config({ 45 | driver: localforage.INDEXEDDB, 46 | name: 'WEB_SCM', 47 | storeName: 'WEB_SCM_TABLE', 48 | }); 49 | 50 | commands.registerCommand('web-scm.localforage.get', async (key) => { 51 | return await localforage.getItem(key); 52 | }); 53 | commands.registerCommand('web-scm.localforage.set', async (key, value) => { 54 | return await localforage.setItem(key, value); 55 | }); 56 | commands.registerCommand('web-scm.localforage.remove', async (key) => { 57 | return await localforage.removeItem(key); 58 | }); 59 | commands.registerCommand('web-scm.localforage.clear', async () => { 60 | return await localforage.clear(); 61 | }); 62 | commands.registerCommand('web-scm.localforage.iterate', async (basePath) => { 63 | const files: CacheFile[] = []; 64 | await localforage.iterate((value: CacheFile, key: string) => { 65 | if (key.startsWith(basePath)) { 66 | files.push(value); 67 | } 68 | }); 69 | return files; 70 | }); 71 | 72 | commands.registerCommand('web-scm.windowOpen', async (path) => { 73 | if (!path) { 74 | window.location = window.location; 75 | } else { 76 | window.open(path); 77 | } 78 | }); 79 | 80 | /* 81 | 21 修改次数 82 | 22 新增文件 83 | 23 删除文件 84 | 24 提交次数 85 | */ 86 | commands.registerCommand('web-scm.yuyanlog', (code, msg, extra) => { 87 | // 埋点数据 88 | console.log(' >>> log', code, msg, extra); 89 | }); 90 | 91 | commands.registerCommand('alex.gty.workerReady', () => { 92 | return workerReady.resolve(); 93 | }); 94 | 95 | commands.registerCommand('code-service.conflictConfig', () => { 96 | // 测试解决冲突内容 97 | return { 98 | isMergeConflicts: false, 99 | sourceBranch: 'merge4', 100 | targetBranch: 'merge1', 101 | prId: '146206608', 102 | hasTag: false, 103 | from: 0, 104 | }; 105 | }); 106 | }; 107 | -------------------------------------------------------------------------------- /collaboration/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | import { AppRenderer, SlotLocation, BoxPanel, SlotRenderer } from '@codeblitzjs/ide-core'; 5 | import '@codeblitzjs/ide-core/languages/cpp'; 6 | import '@codeblitzjs/ide-core/languages/java'; 7 | import '@codeblitzjs/ide-core/languages/javascript'; 8 | import '@codeblitzjs/ide-core/languages/typescript'; 9 | import '@codeblitzjs/ide-core/languages/php'; 10 | import '@codeblitzjs/ide-core/languages/html'; 11 | import '@codeblitzjs/ide-core/languages/css'; 12 | import '@codeblitzjs/ide-core/languages/ruby'; 13 | import '@codeblitzjs/ide-core/languages/go'; 14 | import '@codeblitzjs/ide-core/languages/python'; 15 | 16 | import css from '@codeblitzjs/ide-core/extensions/codeblitz.css-language-features-worker'; 17 | import html from '@codeblitzjs/ide-core/extensions/codeblitz.html-language-features-worker'; 18 | import typescript from '@codeblitzjs/ide-core/extensions/codeblitz.typescript-language-features-worker'; 19 | // import collaboration from '@codeblitzjs/ide-core/extensions/codeblitz.collaboration'; 20 | import * as plugin from './plugin'; 21 | 22 | import Modal from 'antd/lib/modal' 23 | import 'antd/lib/modal/style' 24 | import Input from 'antd/lib/input' 25 | import 'antd/lib/input/style' 26 | import Button from 'antd/lib/button' 27 | 28 | export const layoutConfig = { 29 | [SlotLocation.main]: { 30 | modules: ['@opensumi/ide-editor'], 31 | }, 32 | }; 33 | 34 | const LayoutComponent = () => ( 35 | 36 | 37 | 38 | ); 39 | 40 | const App = () => { 41 | const [open, setOpen] = useState(false) 42 | const [ready, setReady] = useState(false) 43 | const [value, setValue] = useState('') 44 | 45 | const handleOk = () => { 46 | const name = value.trim() 47 | if (name) { 48 | sessionStorage.setItem('collaboration-name', name) 49 | plugin.initSocket(name) 50 | setReady(true) 51 | setOpen(false) 52 | } 53 | } 54 | 55 | useEffect(() => { 56 | const name = sessionStorage.getItem('collaboration-name') 57 | if (name) { 58 | setReady(true) 59 | plugin.initSocket(name) 60 | } else { 61 | setOpen(true) 62 | } 63 | }, []) 64 | 65 | return ( 66 |
67 | setReady(false)} 72 | footer={[ 73 | 74 | ]} 75 | > 76 | setValue(e.target.value)} onPressEnter={handleOk} /> 77 | 78 | {ready && ( 79 | 116 | )} 117 |
118 | ); 119 | }; 120 | 121 | ReactDOM.createRoot(document.getElementById('main')!).render(); 122 | -------------------------------------------------------------------------------- /collaboration/plugin.ts: -------------------------------------------------------------------------------- 1 | import { IPluginAPI } from '@codeblitzjs/ide-core/lib/editor'; 2 | import { io, Socket } from 'socket.io-client'; 3 | import 'antd/lib/message/style' 4 | import message from 'antd/lib/message' 5 | 6 | export enum COLLABORATION_COMMAND { 7 | Reconnect = 'collaboration.reconnect', 8 | Initialize = 'collaboration.initialize', 9 | Initialized = 'collaboration.initialized', 10 | SendOperation = 'collaboration.sendOperation', 11 | ApplyOperation = 'collaboration.applyOperation', 12 | SendSelection = 'collaboration.sendSelection', 13 | ApplySelection = 'collaboration.applySelection', 14 | Join = 'collaboration.join', 15 | Leave = 'collaboration.leave', 16 | SyncState = 'collaboration.syncState', 17 | } 18 | 19 | class Deferred { 20 | resolve: (value: T) => void; 21 | reject: (err?: any) => void; 22 | 23 | promise = new Promise((resolve, reject) => { 24 | this.resolve = resolve; 25 | this.reject = reject; 26 | }); 27 | } 28 | 29 | const extReady = new Deferred(); 30 | const initStateReady = new Deferred(); 31 | const initializedStateReady = new Deferred(); 32 | export const commandReady = new Deferred(); 33 | export let commands: IPluginAPI['commands']; 34 | 35 | 36 | let socket: Socket | null = null; 37 | const currentUser = { 38 | name: '', 39 | userId: Math.ceil(Math.random() * 10000), 40 | } 41 | 42 | export const initSocket = (name: string) => { 43 | currentUser.name = name; 44 | socket = io(`http://${process.env.HOST || 'localhost'}:9099`); 45 | socket 46 | .on('connect', async () => { 47 | socket!.emit('join', currentUser) 48 | socket!.emit('state', (data) => { 49 | initStateReady.resolve({ 50 | status: 'success', 51 | data: { 52 | ...currentUser, 53 | revision: data.revision, 54 | code: data.code, 55 | mode: 'javascript', 56 | }, 57 | }); 58 | }); 59 | }) 60 | .on('join', (data) => { 61 | initializedStateReady.promise.then(() => { 62 | commands.executeCommand(COLLABORATION_COMMAND.Join, data); 63 | }) 64 | message.info(`${data.name} 加入了`) 65 | }) 66 | .on('leave', (data) => { 67 | initializedStateReady.promise.then(() => { 68 | commands.executeCommand(COLLABORATION_COMMAND.Leave, data?.userId); 69 | }) 70 | message.info(`${data.name} 离开了`) 71 | }) 72 | .on('reconnect', async () => { 73 | executeCommand(COLLABORATION_COMMAND.Reconnect); 74 | }) 75 | .on('operation', (data) => { 76 | executeCommand(COLLABORATION_COMMAND.ApplyOperation, data); 77 | }) 78 | .on('selection', (data) => { 79 | executeCommand(COLLABORATION_COMMAND.ApplySelection, data); 80 | }); 81 | } 82 | 83 | const executeCommand = async (commandId: COLLABORATION_COMMAND, data?: any) => { 84 | await extReady.promise; 85 | commands.executeCommand(commandId, data); 86 | }; 87 | 88 | export const PLUGIN_ID = 'live'; 89 | 90 | export const activate = ({ context, commands: commands_ }: IPluginAPI) => { 91 | commands = commands_; 92 | 93 | commandReady.resolve(commands_); 94 | 95 | context.subscriptions.push( 96 | commands.registerCommand(COLLABORATION_COMMAND.Initialize, async () => { 97 | extReady.resolve(); 98 | return initStateReady.promise; 99 | }), 100 | 101 | commands.registerCommand(COLLABORATION_COMMAND.Initialized, () => { 102 | initializedStateReady.resolve(); 103 | }), 104 | 105 | commands.registerCommand(COLLABORATION_COMMAND.SyncState, async () => { 106 | return new Promise((resolve) => { 107 | socket?.emit('state', resolve); 108 | }); 109 | }), 110 | 111 | commands.registerCommand(COLLABORATION_COMMAND.SendOperation, (data) => { 112 | return new Promise((resolve, reject) => { 113 | socket?.emit('operation', { ...data, ...currentUser }, (success) => { 114 | if (success) { 115 | resolve(); 116 | } else { 117 | reject(); 118 | } 119 | }); 120 | }); 121 | }), 122 | 123 | commands.registerCommand(COLLABORATION_COMMAND.SendSelection, (data) => { 124 | return new Promise((resolve, reject) => { 125 | socket?.emit('selection', { ...data, ...currentUser }, resolve); 126 | }); 127 | }) 128 | ); 129 | }; 130 | -------------------------------------------------------------------------------- /collaboration/server.js: -------------------------------------------------------------------------------- 1 | const app = require('http').createServer(); 2 | const { Server } = require('socket.io'); 3 | const { TextOperation, Server: OTServer } = require('ot'); 4 | 5 | const io = new Server(app, { 6 | cors: true, 7 | }); 8 | 9 | app.listen(9099); 10 | 11 | const otServer = new OTServer(''); 12 | 13 | io.on('connection', function (socket) { 14 | let user = null 15 | socket.on('join', (data) => { 16 | user = data 17 | socket.broadcast.emit('join', data); 18 | }) 19 | 20 | socket.on('state', (fn) => { 21 | fn({ 22 | revision: otServer.operations ? otServer.operations.length : 0, 23 | code: otServer.document, 24 | }) 25 | }); 26 | 27 | socket.on('operation', function (data, fn) { 28 | const operation = TextOperation.fromJSON(data.ops); 29 | try { 30 | const transformedOperation = otServer.receiveOperation(data.revision, operation); 31 | fn(true); 32 | socket.broadcast.emit('operation', { ops: transformedOperation.toJSON(), userId: data.userId }); 33 | } catch (err) { 34 | console.error(err); 35 | fn(false); 36 | } 37 | }); 38 | 39 | socket.on('selection', (data) => { 40 | socket.broadcast.emit('selection', data); 41 | }); 42 | 43 | socket.on('disconnect', () => { 44 | socket.broadcast.emit('leave', user); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /common/code-blame.plugin.ts: -------------------------------------------------------------------------------- 1 | import { IPluginModule, IPluginAPI } from '@codeblitzjs/ide-core/lib/editor'; 2 | 3 | export enum ExtensionCommand { 4 | linkToCommit = 'code.blame.linktocommit', 5 | onActive = 'code.blame.extension.active', 6 | setProjectData = 'code.blame.setProjectData', 7 | getBlameData = 'code.blame.getBlameData', 8 | } 9 | 10 | export default class IDEPlugin implements IPluginModule { 11 | commands: IPluginAPI['commands']; 12 | onActivate: () => void; 13 | linkToCommit: (commitId: string) => void; 14 | getBlame: (projectId: number, commitId: string, path: string) => Promise; 15 | 16 | /** 17 | * 插件 ID,用于唯一标识插件 18 | */ 19 | PLUGIN_ID = 'ACR_BLAME_PLUGIN'; 20 | 21 | constructor( 22 | onActive: () => void, 23 | linkToCommit: (commitId: string) => void, 24 | getBlame: (projectId: number, commitId: string, path: string) => Promise 25 | ) { 26 | this.onActivate = onActive; 27 | this.linkToCommit = linkToCommit; 28 | this.getBlame = getBlame; 29 | } 30 | 31 | /** 32 | * 激活插件 33 | */ 34 | activate = (ctx: IPluginAPI) => { 35 | const { commands, context } = ctx; 36 | this.commands = commands; 37 | context.subscriptions.push( 38 | commands.registerCommand(ExtensionCommand.onActive, () => { 39 | this.onActivate(); 40 | }), 41 | commands.registerCommand(ExtensionCommand.linkToCommit, (params) => { 42 | const { commitId } = params; 43 | this.linkToCommit(commitId); 44 | }), 45 | commands.registerCommand(ExtensionCommand.getBlameData, async (sendData) => { 46 | const { projectId, prevSha, nextSha, filePath } = sendData; 47 | // kaitian内uri 和 antcode内不同 多了一个 / 48 | const path = filePath.startsWith('/') ? filePath.slice(1) : filePath; 49 | const response = await this.getBlame(projectId, nextSha, path).then((res) => { 50 | return res; 51 | }); 52 | return response; 53 | }) 54 | ); 55 | }; 56 | 57 | /** 58 | * 注销插件,可在此时机清理副作用 59 | */ 60 | deactivate() {} 61 | } 62 | -------------------------------------------------------------------------------- /common/code-scaning.plugin.ts: -------------------------------------------------------------------------------- 1 | import { IPluginAPI, IPluginModule } from '@codeblitzjs/ide-core/lib/editor'; 2 | 3 | export class Plugin implements IPluginModule { 4 | PLUGIN_ID = 'CODE_SCANING'; 5 | 6 | private _commands: IPluginAPI['commands'] | null = null; 7 | 8 | private _ready: boolean = false; 9 | 10 | private props: null = null; 11 | 12 | get commands() { 13 | return this._commands; 14 | } 15 | 16 | get ready() { 17 | return this._ready; 18 | } 19 | 20 | setProps(props) { 21 | this.props = props; 22 | } 23 | 24 | async activate({ context, commands }: IPluginAPI) { 25 | this._commands = commands; 26 | context.subscriptions.push( 27 | commands.registerCommand('antcode-cr.plugin.props', async () => { 28 | this._ready = true; 29 | return this.props; 30 | }) 31 | ); 32 | } 33 | 34 | deactivate() { 35 | this._commands = null; 36 | } 37 | } 38 | 39 | export default new Plugin(); 40 | -------------------------------------------------------------------------------- /common/local-extension.module.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Provider, Injectable, Autowired } from '@opensumi/di'; 3 | import { 4 | Domain, 5 | URI, 6 | AppConfig, 7 | BrowserModule, 8 | FsProviderContribution, 9 | } from '@opensumi/ide-core-browser'; 10 | import { ExtensionServiceClientImpl, IExtensionNodeClientService } from '@codeblitzjs/ide-core-core'; 11 | import { IExtensionMetadata, IExtensionBasicMetadata } from '@codeblitzjs/ide-core-shared'; 12 | import { IFileServiceClient } from '@opensumi/ide-file-service'; 13 | import { KaitianExtFsProvider } from '@codeblitzjs/ide-core-core'; 14 | 15 | import { 16 | StaticResourceContribution, 17 | StaticResourceService, 18 | } from '@opensumi/ide-static-resource/lib/browser'; 19 | 20 | const KT_EXT_LOCAL_SCHEME = 'kt-ext-local'; 21 | 22 | /** 23 | * 这里不使用框架的 express-file-server 的文件形式 /assets?path=file 这种无法自动加载相对路径的 sourcemap 文件 24 | */ 25 | @Domain(StaticResourceContribution, FsProviderContribution) 26 | export class FileServerContribution implements StaticResourceContribution, FsProviderContribution { 27 | @Autowired(AppConfig) 28 | appConfig: AppConfig; 29 | 30 | @Autowired() 31 | private readonly ktExtFsProvider: KaitianExtFsProvider; 32 | 33 | registerStaticResolver(service: StaticResourceService): void { 34 | service.registerStaticResourceProvider({ 35 | scheme: KT_EXT_LOCAL_SCHEME, 36 | resolveStaticResource: (uri: URI) => { 37 | return uri.withScheme(location.protocol.slice(0, -1)); 38 | }, 39 | roots: [location.origin], 40 | }); 41 | } 42 | 43 | registerProvider(registry: IFileServiceClient) { 44 | registry.registerProvider(KT_EXT_LOCAL_SCHEME, this.ktExtFsProvider); 45 | } 46 | } 47 | 48 | /** 49 | * 加上本地调试用的插件 50 | * dir toolkit/extensions, toolkit/fixtures 51 | */ 52 | @Injectable() 53 | class ExtensionServiceClient extends ExtensionServiceClientImpl { 54 | async getAllExtensions(...args: any[]) { 55 | const remoteExtensions = await super.getAllExtensions(args[0], args[1], args[2], args[3]); 56 | const res = await fetch('/getLocalExtensions'); 57 | const localExtensions: IExtensionMetadata[] = await res.json(); 58 | // 转换类似纯前端下的 scheme path,保持和实际运行一致,否则对于本地资源和实际使用的 bfs 不一致 59 | localExtensions.forEach((ext) => { 60 | const extensionUri = URI.from({ 61 | scheme: KT_EXT_LOCAL_SCHEME, 62 | authority: location.host, 63 | path: `/assets/~${ext.path}`, 64 | }); 65 | ext.path = ext.realPath = extensionUri.toString(true); 66 | ext.uri = extensionUri.codeUri; 67 | }); 68 | return [...remoteExtensions, ...localExtensions]; 69 | } 70 | } 71 | 72 | @Injectable() 73 | export class LocalExtensionModule extends BrowserModule { 74 | providers: Provider[] = [ 75 | FileServerContribution, 76 | { 77 | token: IExtensionNodeClientService, 78 | useClass: ExtensionServiceClient, 79 | override: true, 80 | }, 81 | ]; 82 | } 83 | 84 | export const useLoadLocalExtensionMetadata = () => { 85 | const [extensions, setExtensions] = useState(null); 86 | useEffect(() => { 87 | (async () => { 88 | const res = await fetch('/getLocalExtensionsMetadata'); 89 | const localExtensions: IExtensionBasicMetadata[] = await res.json(); 90 | setExtensions(localExtensions); 91 | })(); 92 | }, []); 93 | return extensions; 94 | }; 95 | -------------------------------------------------------------------------------- /common/plugin.ts: -------------------------------------------------------------------------------- 1 | import { IPluginAPI, IPluginModule } from '@codeblitzjs/ide-core/lib/editor'; 2 | 3 | export class Plugin implements IPluginModule { 4 | PLUGIN_ID = 'PLUGIN_TEST'; 5 | 6 | private _commands: IPluginAPI['commands'] | null = null; 7 | 8 | get commands() { 9 | return this._commands; 10 | } 11 | 12 | activate({ context, commands }: IPluginAPI) { 13 | this._commands = commands; 14 | context.subscriptions.push( 15 | commands.registerCommand('plugin.command.add', (x: number) => { 16 | commands.executeCommand('plugin.command.say', 'alex is great'); 17 | return x + x; 18 | }) 19 | ); 20 | } 21 | } 22 | 23 | export default new Plugin(); 24 | -------------------------------------------------------------------------------- /common/shims/react-dom-client.js: -------------------------------------------------------------------------------- 1 | // src/shims/react-dom-client.js 2 | import ReactDOM from 'react-dom'; 3 | 4 | export function createRoot(container) { 5 | return { 6 | render: (element) => ReactDOM.render(element, container), 7 | }; 8 | } -------------------------------------------------------------------------------- /common/zipPlugin.ts: -------------------------------------------------------------------------------- 1 | import { IPluginAPI, IPluginModule } from '@codeblitzjs/ide-core/lib/editor'; 2 | 3 | export class Plugin implements IPluginModule { 4 | PLUGIN_ID = 'PLUGIN_TEST'; 5 | 6 | private _commands: IPluginAPI['commands'] | null = null; 7 | 8 | get commands() { 9 | return this._commands; 10 | } 11 | fullScreenOff: ()=> void 12 | fullScreenOn: ()=> void 13 | fullScreenStatus: boolean 14 | constructor( 15 | fullScreenOn: () => void, 16 | fullScreenOff:()=> void, 17 | fullScreenStatus: boolean 18 | ) { 19 | this.fullScreenOn = fullScreenOn; 20 | this.fullScreenOff = fullScreenOff; 21 | this.fullScreenStatus = fullScreenStatus 22 | } 23 | 24 | activate({ context, commands }: IPluginAPI) { 25 | this._commands = commands; 26 | context.subscriptions.push( 27 | commands.registerCommand('alex.zip.setFullScreen', (x: number) => { 28 | if(this.fullScreenStatus){ 29 | this.fullScreenOff() 30 | }else { 31 | this.fullScreenOn() 32 | } 33 | this.fullScreenStatus = !this.fullScreenStatus 34 | return 35 | }) 36 | ); 37 | } 38 | } 39 | 40 | export default Plugin -------------------------------------------------------------------------------- /components/ComponentMenuGroupDivider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const ComponentMenuGroupDivider: React.FC = () => { 4 | return
111
; 5 | }; 6 | -------------------------------------------------------------------------------- /components/zipButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { requireModule } from '@codeblitzjs/ide-core/bundle'; 3 | import JSZip from 'jszip'; 4 | import { saveAs } from 'file-saver'; 5 | import { FullScreenToken } from '../zip'; 6 | 7 | export interface FileStat { 8 | /** 9 | * 资源路径 10 | */ 11 | uri: string; 12 | 13 | /** 14 | * 资源最后修改时间 15 | */ 16 | lastModification: number; 17 | 18 | /** 19 | * 资源的创建时间 20 | */ 21 | createTime?: number; 22 | 23 | /** 24 | * 资源是否为文件夹 25 | */ 26 | isDirectory: boolean; 27 | 28 | /** 29 | * 资源是否为软连接 30 | */ 31 | isSymbolicLink?: boolean; 32 | 33 | /** 34 | * 资源是否在软连接文件夹内 35 | */ 36 | isInSymbolicDirectory?: boolean; 37 | 38 | /** 39 | * The children of the file stat. 40 | * If it is `undefined` and `isDirectory` is `true`, then this file stat is unresolved. 41 | */ 42 | children?: FileStat[]; 43 | 44 | /** 45 | * The size of the file if known. 46 | */ 47 | size?: number; 48 | 49 | /** 50 | * 同 vscode FileType 51 | */ 52 | type?: FileType; 53 | 54 | /** 55 | * 当前文件是否为只读 56 | */ 57 | readonly?: boolean; 58 | } 59 | 60 | export enum FileType { 61 | /** 62 | * The file type is unknown. 63 | */ 64 | Unknown = 0, 65 | /** 66 | * A regular file. 67 | */ 68 | File = 1, 69 | /** 70 | * A directory. 71 | */ 72 | Directory = 2, 73 | /** 74 | * A symbolic link to a file. 75 | */ 76 | SymbolicLink = 64, 77 | } 78 | 79 | export interface MapStat { 80 | /** 81 | * 资源路径 82 | */ 83 | uri: string; 84 | 85 | /** 86 | * 资源是否为文件夹 87 | */ 88 | isDirectory: boolean; 89 | 90 | /** 91 | * 同 vscode FileType 92 | */ 93 | type?: FileType; 94 | 95 | content: string | null; 96 | children?: FileStat[]; 97 | } 98 | const FileService = requireModule('@opensumi/ide-file-service'); 99 | const CoreBrowser = requireModule('@opensumi/ide-core-browser'); 100 | const { Injectable, Injector } = requireModule('@opensumi/di'); 101 | 102 | const fs = requireModule('fs'); 103 | 104 | const { Button } = requireModule('@opensumi/ide-components'); 105 | 106 | const { useInjectable, URI, CommandService } = CoreBrowser; 107 | 108 | let fileMap = new Map(); 109 | 110 | export async function getAllFiles(path: string, fileService) { 111 | if (path.includes('node_modules')) { 112 | return; 113 | } 114 | const uri = URI.parse(path).toString(); 115 | const res: FileStat = await fileService.getFileStat(uri); 116 | // @ts-ignore 117 | let content: null | BinaryBuffer = null; 118 | if (!res.isDirectory) { 119 | content = await fileService.readFile(uri); 120 | } 121 | 122 | fileMap.set(uri, { 123 | uri: res.uri, 124 | isDirectory: res.isDirectory, 125 | type: res.type, 126 | content: content ? content.content.toString() : null, 127 | children: res.children, 128 | }); 129 | 130 | if (res) { 131 | if (res.isDirectory) { 132 | const promiseAll = res.children!.map(child => { 133 | return getAllFiles(child.uri, fileService); 134 | }); 135 | await Promise.all(promiseAll); 136 | } 137 | } 138 | } 139 | 140 | // zip 打包 141 | export const click = async fileService => { 142 | fileMap = new Map(); 143 | // 获取所有文件列表 144 | await getAllFiles(rootDir, fileService); 145 | console.log(fileMap); 146 | const zip = new JSZip(); 147 | for (let [key, value] of fileMap.entries()) { 148 | const fullPath = key.slice(rootDir.length + 1); 149 | const name = fullPath.split('/').pop(); 150 | if (value.isDirectory && value.children?.length) { 151 | zip.folder(fullPath); 152 | } else { 153 | zip.file(fullPath, value.content as string); 154 | } 155 | } 156 | 157 | zip.generateAsync({ type: 'blob' }).then(function (content) { 158 | saveAs(content, 'test1.zip'); 159 | }); 160 | }; 161 | 162 | export const rootDir = 'file:///workspace/zip_file_system'; 163 | 164 | export function FullScreenBtn() { 165 | const commandService = useInjectable(CommandService); 166 | 167 | const click = async () => { 168 | commandService.executeCommand('alex.zip.setFullScreen'); 169 | }; 170 | return ( 171 |
172 | 175 |
176 | ); 177 | } 178 | -------------------------------------------------------------------------------- /editor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo } from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | // 如不需要扩展,从 codeblitz.editor 引入 4 | import { IAppInstance, EditorRenderer } from "@codeblitzjs/ide-core/lib/editor.all"; 5 | import { request } from '@codeblitzjs/ide-common'; 6 | 7 | import "@codeblitzjs/ide-core/bundle/codeblitz.editor.all.css"; 8 | import "antd/dist/antd.css"; 9 | 10 | import Select from "antd/lib/select"; 11 | import Cascader from "antd/lib/cascader"; 12 | import Button from "antd/lib/button"; 13 | import Spin from "antd/lib/spin"; 14 | 15 | //#region 语法高亮,根据需要动态注册 16 | import "@codeblitzjs/ide-core/languages/html"; 17 | import "@codeblitzjs/ide-core/languages/css"; 18 | import "@codeblitzjs/ide-core/languages/javascript"; 19 | import "@codeblitzjs/ide-core/languages/typescript"; 20 | import "@codeblitzjs/ide-core/languages/json"; 21 | import "@codeblitzjs/ide-core/languages/markdown"; 22 | import "@codeblitzjs/ide-core/languages/java"; 23 | //#endregion 24 | 25 | import WorkerExample from "./extensions/worker-example/worker-example"; 26 | 27 | import * as EditorPlugin from "./plugin"; 28 | 29 | const fileOptions = (function transform(obj) { 30 | return Object.keys(obj).map((key) => { 31 | return { 32 | value: key, 33 | label: key, 34 | children: Array.isArray(obj[key]) 35 | ? obj[key].map((v) => ({ value: v, label: v })) 36 | : transform(obj[key]), 37 | }; 38 | }); 39 | })({ 40 | 'opensumi/core': { 41 | main: [ 42 | 'README.md', 43 | 'package.json' 44 | ], 45 | }, 46 | 'opensumi/codeblitz': { 47 | main: [ 48 | 'README.md', 49 | 'package.json' 50 | ], 51 | } 52 | }); 53 | 54 | const App = () => { 55 | const [key, setKey] = useState(0); 56 | const [project, setProject] = useState(""); 57 | const [ref, setRef] = useState(""); 58 | const [filepath, setFilePath] = useState(""); 59 | const [encoding, setEncoding] = useState<"utf8" | "gbk" | undefined>("utf8"); 60 | const [lineNumber, setLineNumber] = useState< 61 | number | [number, number] | undefined 62 | >(); 63 | 64 | const onFileChange = ([project, ref, filepath]) => { 65 | setProject(project); 66 | setRef(ref); 67 | setFilePath(filepath); 68 | }; 69 | 70 | const readFile = async (filepath: string) => { 71 | const res = await request( 72 | `https://api.github.com/repos/${project}/contents/${filepath}?ref=${ref}`, 73 | { 74 | headers: { 75 | Accept: 'application/vnd.github.v3.raw', 76 | }, 77 | responseType: 'arrayBuffer', 78 | }, 79 | ); 80 | if (res) { 81 | return res 82 | } 83 | throw new Error(`readFile`); 84 | }; 85 | 86 | return ( 87 |
88 |
89 | 96 |
97 |
98 | 105 | 118 | 131 | 142 | 161 | 172 |
173 |
174 |
175 | {project ? ( 176 | { 179 | window.app = app; 180 | }} 181 | Landing={() => ( 182 |
191 | 192 |
193 | )} 194 | appConfig={{ 195 | // plugin 配置 196 | plugins: [EditorPlugin], 197 | // extension 198 | extensionMetadata: [WorkerExample], 199 | // workspaceDir 和标准工作空间概念一样,建议不同项目不同,相对路径即可 200 | workspaceDir: project, 201 | // 默认配置 202 | defaultPreferences: { 203 | // 白色主题 opensumi-design-light-theme opensumi-design-dark-theme 204 | 'general.theme': 'opensumi-design-light-theme', 205 | // 最后一行禁止继续滚动 206 | "editor.scrollBeyondLastLine": false, 207 | // 只读模式,目前只读和读写无法切换,设置了后不可更改 208 | // 'editor.readonlyFiles': ['/workspace/**'], 209 | // 'editor.forceReadOnly': true, 210 | "editor.fontSize": 12, 211 | // 开启 lsif 212 | "lsif.documentScheme": "file", 213 | "lsif.enable": true, 214 | // 设置环境 test | prod,对于测试环境建议设置为 test 此时会请求测试环境的 lsif 服务,可看测试仓库的数据 215 | "lsif.env": "prod", 216 | // 其它配置可以参考 Ant Codespaces 上的支持的配置 217 | // 列举几个可能用得到的 218 | // 如果遇到 widgets 如 hover 被遮盖的情况,将 postion 改为 fixed 219 | "editor.fixedOverflowWidgets": true, 220 | "editor.autoSave": false 221 | }, 222 | }} 223 | runtimeConfig={{ 224 | // 场景,在 editor 下推荐传 null,此时不会持久化缓存工作空间数据 225 | scenario: null, 226 | // 启动时显示的编辑器,editor 下传 none 即可 227 | startupEditor: "none", 228 | // 隐藏 editor tab 229 | hideEditorTab: false, 230 | }} 231 | editorConfig={ 232 | { 233 | // 禁用编辑器搜索,只在想用浏览器搜索的情况下禁用,通常不建议 234 | // disableEditorSearch: true, 235 | // 展开高度,如果想 editor 不适用虚拟滚动,完全展开代码,则开启 236 | // stretchHeight: true, 237 | } 238 | } 239 | // 文档模型,以下数据变更时会更新打开的编辑器 240 | documentModel={{ 241 | // 类型,code 下为 code,保持不变 242 | type: "code", 243 | // 分支或 tag 244 | ref, 245 | // 仓库群组和用户 246 | owner: project.split("/")[0], 247 | // 仓库名 248 | name: project.split("/")[1], 249 | // 仓库文件路径 250 | filepath, 251 | // 读取文件接口 252 | readFile, 253 | // 文件编码,和标准工作空间支持编码类型一致,简单场景只传 utf8 或 gbk 即可 254 | encoding, 255 | // 文件路径变更 256 | onFilepathChange(newFilepath) { 257 | setFilePath(newFilepath); 258 | }, 259 | // 行号设置及变更 260 | lineNumber, 261 | onLineNumberChange(num) { 262 | setLineNumber(num); 263 | }, 264 | }} 265 | /> 266 | ) : null} 267 |
268 |
269 |
270 | ); 271 | }; 272 | 273 | ReactDOM.createRoot(document.getElementById("main")!).render(); 274 | 275 | // for test 276 | window.destroy = () => { 277 | ReactDOM.createRoot(document.getElementById("main")!).render(
destroyed
); 278 | }; 279 | 280 | declare global { 281 | interface Window { 282 | app: IAppInstance; 283 | destroy(): void; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /extensions/collaboration/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | out 3 | node_modules 4 | lib -------------------------------------------------------------------------------- /extensions/collaboration/.sumiignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | **/tsconfig.json 7 | **/tslint.json 8 | **/*.map 9 | **/*.ts 10 | node_modules/ 11 | -------------------------------------------------------------------------------- /extensions/collaboration/.vscodeignore: -------------------------------------------------------------------------------- 1 | ** 2 | !out 3 | !kaitian-meta.json -------------------------------------------------------------------------------- /extensions/collaboration/LEGAL.md: -------------------------------------------------------------------------------- 1 | Legal Disclaimer 2 | 3 | Within this source code, the comments in Chinese shall be the original, governing version. Any comment in other languages are for reference only. In the event of any conflict between the Chinese language version comments and other language version comments, the Chinese language version shall prevail. 4 | 5 | 法律免责声明 6 | 7 | 关于代码注释部分,中文注释为官方版本,其它语言注释仅做参考。中文注释可能与其它语言注释存在不一致,当中文注释与其它语言注释存在不一致时,请以中文注释为准。 -------------------------------------------------------------------------------- /extensions/collaboration/README.md: -------------------------------------------------------------------------------- 1 | # collaboration 2 | >提供协同编辑能力 3 | 4 | ## 开发 5 | ```bash 6 | # 开启扩展编译、live server 以及 playground 7 | # 面试官地址 http://localhost:8001/?tppe=1 8 | # 面试者地址 http://localhost:8001/?tppe=2 9 | 10 | # start 11 | 12 | npm run dev 13 | 14 | # package npm i -g @opensumi/sumi 15 | sumi package 16 | ``` 17 | -------------------------------------------------------------------------------- /extensions/collaboration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collaboration", 3 | "version": "0.1.0", 4 | "description": "extension for collaboration", 5 | "publisher": "codeblitz", 6 | "engines": { 7 | "opensumi": "*", 8 | "kaitian": "*" 9 | }, 10 | "activationEvents": [ 11 | "*" 12 | ], 13 | "kaitianContributes": { 14 | "workerMain": "./out/extension.js" 15 | }, 16 | "enableKaitianWebAssets": true, 17 | "contributes": { 18 | "configuration": { 19 | "title": "collaboration", 20 | "properties": { 21 | "alitcode.debug": { 22 | "type": "boolean", 23 | "scope": "window", 24 | "description": "debug mode", 25 | "default": false 26 | }, 27 | "alitcode.locale": { 28 | "type": "string", 29 | "scope": "window", 30 | "description": "locale", 31 | "default": "zh-CN" 32 | } 33 | } 34 | } 35 | }, 36 | "scripts": { 37 | "start": "yarn dev", 38 | "dev": "webpack --watch --mode development", 39 | "vscode:prepublish": "webpack --mode production" 40 | }, 41 | "author": "", 42 | "license": "ISC", 43 | "devDependencies": { 44 | "@opensumi/sumi": "latest", 45 | "@opensumi/cli": "latest", 46 | "@types/color-string": "^1.5.0", 47 | "@types/lodash.debounce": "^4.0.6", 48 | "@types/react": "^17.0.5", 49 | "@types/react-dom": "^17.0.5", 50 | "css-loader": "^5.2.4", 51 | "file-loader": "^6.2.0", 52 | "glamor": "^2.20.40", 53 | "html-webpack-plugin": "^5.3.1", 54 | "husky": "^4.3.0", 55 | "lint-staged": "^11.0.0", 56 | "npm-run-all": "^4.1.5", 57 | "ot": "^0.0.15", 58 | "prettier": "^2.3.0", 59 | "react": "^17.0.2", 60 | "react-dom": "^17.0.2", 61 | "socket.io": "^4.1.1", 62 | "socket.io-client": "^4.1.2", 63 | "style-loader": "^2.0.0", 64 | "ts-loader": "^9.1.2", 65 | "ts-node": "^9.1.1", 66 | "typescript": "^4.2.4", 67 | "webpack": "^5.37.0", 68 | "webpack-cli": "^4.7.0", 69 | "webpack-dev-server": "^3.11.2" 70 | }, 71 | "dependencies": { 72 | "color-string": "^1.5.5", 73 | "lodash.debounce": "^4.0.8", 74 | "tslib": "^2.2.0" 75 | }, 76 | "husky": { 77 | "hooks": { 78 | "pre-commit": "lint-staged" 79 | } 80 | }, 81 | "lint-staged": { 82 | "*.js": [ 83 | "prettier --write" 84 | ], 85 | "*.ts?(x)": [ 86 | "prettier --parser=typescript --write" 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /extensions/collaboration/src/Live.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExtensionContext, 3 | workspace, 4 | commands, 5 | WorkspaceEdit, 6 | TextEdit, 7 | Range, 8 | Uri, 9 | window, 10 | ViewColumn, 11 | TextEditor, 12 | EndOfLine, 13 | Selection, 14 | languages, 15 | TextDocumentChangeEvent, 16 | } from 'vscode'; 17 | import debounce from 'lodash.debounce'; 18 | 19 | import { convertChangeEventToOperation } from './event-to-transform'; 20 | import { TextOperation } from './ot'; 21 | import { OTClient } from './OTClient'; 22 | import { log, logError, logWarn, t, report } from './utils'; 23 | import { 24 | LIVE_COMMAND, 25 | InitializeState, 26 | ApplyOperationType, 27 | ApplySelectionType, 28 | SendOperationType, 29 | UserProfile, 30 | PREFERENCE_COMMAND, 31 | DocState, 32 | REPORT_NAME, 33 | } from './types'; 34 | import { DecoratorManager, NameTagVisibility } from './decorators'; 35 | import { UsersManager } from './UsersManager'; 36 | 37 | export class Live { 38 | private otClient: OTClient; 39 | 40 | private usersManager: UsersManager; 41 | 42 | private isApplyingOperation: boolean = false; 43 | 44 | private docUri: Uri; 45 | 46 | private editor: TextEditor; 47 | 48 | private code = ''; 49 | 50 | // 是否可编辑,在 editor 未初始化或 rollback 时编辑的内容会被丢弃 51 | private editable = false; 52 | 53 | /** 54 | * 远程 operation 缓存队列 55 | */ 56 | private remoteOperationsQueue: ApplyOperationType[] = []; 57 | /** 58 | * 本地 edit changes 队列 59 | */ 60 | private localChangesQueue: TextDocumentChangeEvent[] = []; 61 | 62 | private onSelectionChangeDebounced: ((selections: ReadonlyArray) => void) & { 63 | cancel(): void; 64 | }; 65 | 66 | private decorationsManagers: Record = {}; 67 | 68 | constructor(context: ExtensionContext) { 69 | this.onSelectionChangeDebounced = debounce(this.onSelectionChanged, 200); 70 | } 71 | 72 | async initialize(docUri: Uri) { 73 | const initState = await commands.executeCommand(LIVE_COMMAND.Initialize); 74 | 75 | if (!initState) { 76 | return Promise.reject('initialize data is undefined'); 77 | } 78 | 79 | if (initState.status === 'fail') { 80 | // 表示初始化失败,此时不初始化 editor 81 | return; 82 | } 83 | 84 | const { data } = initState; 85 | 86 | this.usersManager = new UsersManager({ 87 | userId: data.userId, 88 | name: data.name, 89 | }); 90 | 91 | this.otClient = new OTClient(data.revision || 0, this.onSendOperation, this.onApplyOperation); 92 | 93 | this.code = data.code; 94 | this.docUri = docUri; 95 | await workspace.fs.writeFile(docUri, Uint8Array.from([]), { 96 | create: true, 97 | overwrite: true, 98 | }); 99 | const doc = await workspace.openTextDocument(docUri); 100 | this.editor = await window.showTextDocument(doc, { 101 | viewColumn: ViewColumn.One, 102 | preserveFocus: true, 103 | preview: false, 104 | }); 105 | if (data.mode) { 106 | languages.setTextDocumentLanguage(doc, data.mode); 107 | } 108 | const edit = new WorkspaceEdit(); 109 | edit.replace( 110 | docUri, 111 | new Range(doc.lineAt(0).range.start, doc.lineAt(doc.lineCount - 1).range.end), 112 | data.code 113 | ); 114 | await workspace.applyEdit(edit); 115 | 116 | this.startListen(); 117 | 118 | this.editable = true; 119 | 120 | commands.executeCommand(LIVE_COMMAND.Initialized); 121 | } 122 | 123 | async startListen() { 124 | workspace.onDidChangeTextDocument((event) => { 125 | if (event.document.uri.toString() !== this.docUri.toString()) { 126 | return; 127 | } 128 | 129 | this.localChangesQueue.push(event); 130 | this.processQueuedMessages(); 131 | }); 132 | 133 | window.onDidChangeTextEditorSelection(({ selections }) => { 134 | if (this.isApplyingOperation) { 135 | return; 136 | } 137 | this.onSelectionChangeDebounced.cancel(); 138 | this.onSelectionChangeDebounced(selections); 139 | }); 140 | 141 | commands.registerCommand(PREFERENCE_COMMAND.Language, (mode) => { 142 | return languages.setTextDocumentLanguage(this.editor.document, mode || 'plaintext'); 143 | }); 144 | 145 | commands.registerCommand(LIVE_COMMAND.Join, (user: UserProfile) => { 146 | this.usersManager.addUser(user); 147 | }); 148 | 149 | commands.registerCommand(LIVE_COMMAND.Leave, (userId: number) => { 150 | this.usersManager.removeUser(userId); 151 | this.usersManager.removeSelection(userId); 152 | const decoration = this.decorationsManagers[userId]; 153 | if (decoration) { 154 | decoration.dispose(); 155 | delete this.decorationsManagers[userId]; 156 | } 157 | }); 158 | 159 | commands.registerCommand(LIVE_COMMAND.Reconnect, () => { 160 | this.syncState('reconnect'); 161 | }); 162 | 163 | commands.registerCommand(LIVE_COMMAND.ApplyOperation, (data: ApplyOperationType) => { 164 | this.remoteOperationsQueue.push(data); 165 | this.processQueuedMessages(); 166 | }); 167 | 168 | commands.registerCommand(LIVE_COMMAND.ApplySelection, (data: ApplySelectionType) => { 169 | const { userId, selection } = data; 170 | const { document: doc } = this.editor; 171 | const { name } = this.usersManager.getUser(userId)!; 172 | if (selection) { 173 | this.usersManager.setSelection( 174 | userId, 175 | selection.map((item) => ({ 176 | range: new Range(doc.positionAt(item.offset[0]), doc.positionAt(item.offset[1])), 177 | isReversed: item.isReversed, 178 | })) 179 | ); 180 | } 181 | if (!this.decorationsManagers[userId]) { 182 | this.decorationsManagers[userId] = new DecoratorManager( 183 | userId, 184 | name, 185 | NameTagVisibility.Activity, 186 | this.usersManager, 187 | this.editor 188 | ); 189 | } 190 | this.decorationsManagers[userId].updateDecorators(); 191 | }); 192 | 193 | commands.registerCommand(LIVE_COMMAND.UpdateUser, ({ userId, name }: UserProfile) => { 194 | if (!this.decorationsManagers[userId]) { 195 | this.decorationsManagers[userId] = new DecoratorManager( 196 | userId, 197 | name, 198 | NameTagVisibility.Activity, 199 | this.usersManager, 200 | this.editor 201 | ); 202 | } else { 203 | this.decorationsManagers[userId].updateNameTag(name); 204 | } 205 | }); 206 | } 207 | 208 | private processQueuedMessages() { 209 | if (!this.editable) { 210 | logWarn("can't edit before sync code from server"); 211 | return; 212 | } 213 | 214 | if (this.isApplyingOperation) { 215 | log('applying operation'); 216 | return; 217 | } 218 | 219 | const { localChangesQueue } = this; 220 | this.localChangesQueue = []; 221 | while (localChangesQueue.length > 0) { 222 | let localChange = localChangesQueue.shift()!; 223 | this.acceptLocalChange(localChange); 224 | } 225 | 226 | if (this.remoteOperationsQueue.length) { 227 | this.applyingRemoteChange(this.remoteOperationsQueue[0]); 228 | } 229 | } 230 | 231 | private acceptLocalChange(e: TextDocumentChangeEvent) { 232 | const { operation } = convertChangeEventToOperation(e.contentChanges, this.code); 233 | this.sendCodeUpdate(operation); 234 | this.code = this.editor.document.getText(); 235 | } 236 | 237 | private applyingRemoteChange(data: ApplyOperationType) { 238 | try { 239 | this.otClient.applyServer(TextOperation.fromJSON(data.ops)); 240 | } catch (err) { 241 | // 同步失败,可能中间丢失了某写版本,需重新拉取最新版本 242 | logError('同步 ot 操作失败'); 243 | logError({ 244 | category: 'ot', 245 | message: `Apply operation from server to OT client failed ${JSON.stringify(data)}`, 246 | }); 247 | this.syncState(err); 248 | } 249 | } 250 | 251 | private async syncState(err: Error | string) { 252 | report(REPORT_NAME.SyncState, typeof err === 'string' ? err : err?.message || 'Unknown'); 253 | try { 254 | this.editable = false; 255 | const docState: DocState | undefined = await commands.executeCommand(LIVE_COMMAND.SyncState); 256 | if (!docState) { 257 | throw new Error('doc is empty'); 258 | } 259 | this.otClient.reset(docState.revision); 260 | const { 261 | docUri, 262 | editor: { document: doc }, 263 | } = this; 264 | const edit = new WorkspaceEdit(); 265 | edit.replace( 266 | docUri, 267 | new Range(doc.lineAt(0).range.start, doc.lineAt(doc.lineCount - 1).range.end), 268 | docState.code 269 | ); 270 | // 保留上次 selections 271 | const currentSelections = this.editor.selections; 272 | await workspace.applyEdit(edit); 273 | this.editor.selections = currentSelections; 274 | this.code = doc.getText(); 275 | 276 | this.localChangesQueue.length = 0; 277 | this.remoteOperationsQueue.length = 0; 278 | this.editable = true; 279 | } catch (err) { 280 | this.editable = true; 281 | window.showErrorMessage( 282 | t(['Sync code error, please refresh browser', '同步代码失败,请刷新浏览器']) 283 | ); 284 | report(REPORT_NAME.SyncStateFail); 285 | } 286 | } 287 | 288 | private onSelectionChanged(selections: ReadonlyArray) { 289 | const { document: doc } = this.editor; 290 | const data = selections.map((selection) => ({ 291 | offset: [doc.offsetAt(selection.anchor), doc.offsetAt(selection.active)], 292 | isReversed: selection.isReversed, 293 | })); 294 | commands.executeCommand(LIVE_COMMAND.SendSelection, { selection: data }); 295 | } 296 | 297 | private onSendOperation = async (revision: number, operation: TextOperation) => { 298 | log({ 299 | category: 'ot', 300 | message: `Sending ${JSON.stringify({ 301 | revision, 302 | operation, 303 | })}`, 304 | }); 305 | 306 | try { 307 | await commands.executeCommand(LIVE_COMMAND.SendOperation, { 308 | revision, 309 | ops: operation.toJSON(), 310 | }); 311 | } catch (err) { 312 | this.syncState(err); 313 | throw err; 314 | } 315 | }; 316 | 317 | private onApplyOperation = async (operation: TextOperation) => { 318 | this.isApplyingOperation = true; 319 | try { 320 | await this.applyOperationToModel(operation, false); 321 | this.remoteOperationsQueue.shift(); 322 | // 丢弃因 apply operation 引起的本地变更 323 | this.localChangesQueue.shift(); 324 | this.isApplyingOperation = false; 325 | this.code = this.editor.document.getText(); 326 | this.processQueuedMessages(); 327 | } catch (err) { 328 | logError('applying edits failed', err); 329 | this.isApplyingOperation = false; 330 | this.syncState(err); 331 | } 332 | }; 333 | 334 | private async applyOperationToModel(operation: TextOperation, pushStack = false) { 335 | const { document: doc } = this.editor; 336 | const edit = new WorkspaceEdit(); 337 | 338 | let index = 0; 339 | const { eol: currentEOL } = doc; 340 | let eolChanged = false; 341 | const modelCode = doc.getText(); 342 | 343 | if (operation.baseLength !== modelCode.length) { 344 | throw new Error("The base length of the operation doesn't match the length of the code"); 345 | } 346 | 347 | for (let i = 0; i < operation.ops.length; i++) { 348 | const op = operation.ops[i]; 349 | if (TextOperation.isRetain(op)) { 350 | index += op as number; 351 | } else if (TextOperation.isInsert(op)) { 352 | const textOp = op as string; 353 | const position = doc.positionAt(index); 354 | edit.insert(this.docUri, position, textOp); 355 | 356 | // if there's a new line 357 | if (/\n/.test(textOp)) { 358 | const eol = /\r\n/.test(textOp) ? EndOfLine.CRLF : EndOfLine.LF; 359 | if (eol !== currentEOL) { 360 | // With this insert the EOL of the document changed on the other side. We need 361 | // to accomodate our EOL to it. 362 | eolChanged = true; 363 | } 364 | } 365 | } else if (TextOperation.isDelete(op)) { 366 | const delOp = op as number; 367 | const from = doc.positionAt(index); 368 | const to = doc.positionAt(index - delOp); 369 | edit.delete(this.docUri, new Range(from, to)); 370 | index -= delOp; 371 | } 372 | } 373 | 374 | if (eolChanged) { 375 | edit.set(this.docUri, [ 376 | TextEdit.setEndOfLine(currentEOL === EndOfLine.CRLF ? EndOfLine.LF : EndOfLine.CRLF), 377 | ]); 378 | } 379 | 380 | await workspace.applyEdit(edit); 381 | // await doc.save(); 382 | } 383 | 384 | sendCodeUpdate(operation: TextOperation) { 385 | if (!operation) { 386 | return; 387 | } 388 | 389 | if (operation.ops.length === 1) { 390 | const [op] = operation.ops; 391 | if (typeof op === 'number' && op >= 0) { 392 | // Useless to send a single retain operation, ignore 393 | return; 394 | } 395 | } 396 | 397 | try { 398 | this.otClient.applyClient(operation); 399 | } catch (e) { 400 | e.name = 'OperationFailure'; 401 | logError(e); 402 | this.syncState(e); 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /extensions/collaboration/src/OTClient.ts: -------------------------------------------------------------------------------- 1 | import { Blocker, blocker } from './blocker'; 2 | import { TextOperation, SerializedTextOperation, Client, synchronized_ } from './ot'; 3 | import { log, logError } from './utils'; 4 | 5 | export type SendOperationResponse = 6 | | { 7 | composed_operation: SerializedTextOperation; 8 | revision: number; 9 | } 10 | | {}; 11 | 12 | export type SendOperation = (revision: number, operation: TextOperation) => Promise; 13 | 14 | export type ApplyOperation = (operation: TextOperation) => void; 15 | 16 | export class OTClient extends Client { 17 | /* 18 | We need to be able to wait for a client to go intro synchronized 19 | state. The reason is that we want to send a "save" event when the 20 | client is synchronized 21 | */ 22 | public awaitSynchronized: Blocker | null; 23 | 24 | private lastAcknowledgedRevision: number = -1; 25 | 26 | onSendOperation: SendOperation; 27 | 28 | onApplyOperation: ApplyOperation; 29 | 30 | constructor(revision: number, onSendOperation: SendOperation, onApplyOperation: ApplyOperation) { 31 | super(revision); 32 | this.lastAcknowledgedRevision = revision - 1; 33 | this.onSendOperation = onSendOperation; 34 | this.onApplyOperation = onApplyOperation; 35 | } 36 | 37 | async sendOperation(revision: number, operation: TextOperation) { 38 | // Whenever we send an operation we enable the blocker 39 | // that lets us wait for its resolvment when moving back 40 | // to synchronized state 41 | if (!this.awaitSynchronized) { 42 | this.awaitSynchronized = blocker(); 43 | } 44 | 45 | return this.onSendOperation(revision, operation) 46 | .then((result) => { 47 | log({ 48 | category: 'ot', 49 | message: `Acknowledging ${JSON.stringify({ 50 | revision, 51 | operation, 52 | })}`, 53 | }); 54 | 55 | // TODO: 看服务端支持情况 56 | // if ( 57 | // 'revision' in result && 58 | // this.revision !== result.revision && 59 | // result.composed_operation.length 60 | // ) { 61 | // this.resync( 62 | // TextOperation.fromJSON(result.composed_operation), 63 | // result.revision 64 | // ); 65 | // } 66 | 67 | try { 68 | this.safeServerAck(revision); 69 | } catch (err) { 70 | logError( 71 | new Error( 72 | `Server Ack ERROR ${JSON.stringify({ 73 | currentRevision: this.revision, 74 | currentState: this.state.name, 75 | operation, 76 | })}` 77 | ) 78 | ); 79 | } 80 | }) 81 | .catch((error) => { 82 | // If an operation errors on the server we will reject 83 | // the blocker, as an action might be waiting for it to resolve, 84 | // creating a user friendly error related to trying to save 85 | // if (this.awaitSynchronized) { 86 | // this.awaitSynchronized.reject(error); 87 | // } 88 | 89 | logError(error); 90 | 91 | // throw error; 92 | }); 93 | } 94 | 95 | applyOperation(operation: TextOperation) { 96 | this.onApplyOperation(operation); 97 | } 98 | 99 | resetAwaitSynchronized() { 100 | // If we are back in synchronized state we resolve the blocker 101 | if (this.state === synchronized_ && this.awaitSynchronized) { 102 | const awaitSynchronized = this.awaitSynchronized; 103 | this.awaitSynchronized = null; 104 | awaitSynchronized.resolve(); 105 | } 106 | } 107 | 108 | safeServerAck(revision: number) { 109 | // We make sure to not acknowledge the same revision twice 110 | if (this.lastAcknowledgedRevision < revision) { 111 | this.lastAcknowledgedRevision = revision; 112 | super.serverAck(); 113 | } 114 | 115 | this.resetAwaitSynchronized(); 116 | } 117 | 118 | applyClient(operation: TextOperation) { 119 | log({ 120 | category: 'ot', 121 | message: `Apply Client ${JSON.stringify({ 122 | currentRevision: this.revision, 123 | currentState: this.state.name, 124 | operation, 125 | })}`, 126 | }); 127 | 128 | super.applyClient(operation); 129 | } 130 | 131 | applyServer(operation: TextOperation) { 132 | log({ 133 | category: 'ot', 134 | message: `Apply Server ${JSON.stringify({ 135 | currentRevision: this.revision, 136 | currentState: this.state.name, 137 | operation, 138 | })}`, 139 | }); 140 | 141 | super.applyServer(operation); 142 | } 143 | 144 | serverReconnect() { 145 | super.serverReconnect(); 146 | } 147 | 148 | resync(operation: TextOperation, newRevision: number) { 149 | this.applyServer(operation); 150 | this.revision = newRevision; 151 | } 152 | 153 | reset(revision: number) { 154 | this.revision = revision; 155 | this.state = synchronized_; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /extensions/collaboration/src/UsersManager.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { UserProfile, UserSelection } from './types'; 3 | 4 | export class UsersManager { 5 | private users: Record = {}; 6 | private selections: Record = {}; 7 | 8 | constructor(private currentUser: UserProfile) { 9 | this.users[currentUser.userId] = currentUser; 10 | } 11 | 12 | getCurrentUser() { 13 | return this.currentUser; 14 | } 15 | 16 | get currentUserId() { 17 | return this.getCurrentUser().userId; 18 | } 19 | 20 | getUser(id: number): UserProfile | undefined { 21 | return this.users[id]; 22 | } 23 | 24 | addUser(user: UserProfile) { 25 | this.users[user.userId] = user; 26 | } 27 | 28 | removeUser(userId: number) { 29 | delete this.users[userId]; 30 | } 31 | 32 | setSelection(id: number, selections: UserSelection[]) { 33 | this.selections[id] = selections || []; 34 | } 35 | 36 | getSelection(id: number): UserSelection[] | undefined { 37 | return this.selections[id]; 38 | } 39 | 40 | removeSelection(id: number) { 41 | delete this.selections[id]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /extensions/collaboration/src/blocker.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a waiting promise that only resolves when the given value is done 3 | * initializing. It waits. 4 | */ 5 | export type Blocker = { 6 | promise: Promise; 7 | resolve: (value: T) => void; 8 | reject: (value: T) => void; 9 | isFinished: () => boolean; 10 | }; 11 | 12 | export function blocker(): Blocker { 13 | let resolve: (value: any) => void; 14 | let reject: (value: any) => void; 15 | let isFinished = false; 16 | 17 | const promise = new Promise((success, error) => { 18 | resolve = success; 19 | reject = error; 20 | }); 21 | 22 | return { 23 | promise, 24 | resolve: (payload: T) => { 25 | if (isFinished) { 26 | return; 27 | } 28 | 29 | isFinished = true; 30 | resolve(payload); 31 | }, 32 | reject: (payload: any) => { 33 | if (isFinished) { 34 | return; 35 | } 36 | 37 | isFinished = true; 38 | reject(payload); 39 | }, 40 | isFinished: () => isFinished, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /extensions/collaboration/src/event-to-transform.ts: -------------------------------------------------------------------------------- 1 | import { TextDocumentContentChangeEvent } from 'vscode'; 2 | import { TextOperation } from './ot/text-operation'; 3 | 4 | export function convertChangeEventToOperation( 5 | changeEvent: ReadonlyArray, 6 | liveOperationCode: string 7 | ) { 8 | let otOperation: TextOperation; 9 | 10 | let composedCode = liveOperationCode; 11 | 12 | // eslint-disable-next-line no-restricted-syntax 13 | for (const change of changeEvent) { 14 | const newOt = new TextOperation(); 15 | 16 | if (change.rangeOffset !== 0) { 17 | newOt.retain(change.rangeOffset); 18 | } 19 | 20 | if (change.rangeLength > 0) { 21 | newOt.delete(change.rangeLength); 22 | } 23 | 24 | if (change.text) { 25 | newOt.insert(change.text); 26 | } 27 | 28 | const remaining = composedCode.length - newOt.baseLength; 29 | if (remaining > 0) { 30 | newOt.retain(remaining); 31 | } 32 | 33 | otOperation = otOperation! ? otOperation!.compose(newOt) : newOt; 34 | 35 | composedCode = otOperation!.apply(liveOperationCode); 36 | } 37 | 38 | return { 39 | operation: otOperation!, 40 | newCode: composedCode, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /extensions/collaboration/src/extension.ts: -------------------------------------------------------------------------------- 1 | import { workspace, window, ExtensionContext } from "vscode"; 2 | 3 | import { Live } from "./Live"; 4 | import { joinPath, FILE_NAME, t, logError } from "./utils"; 5 | import { initializePreferenceCommands } from "./preference"; 6 | 7 | export const activate = async (context: ExtensionContext) => { 8 | const { workspaceFolders } = workspace; 9 | if (!workspaceFolders?.[0]) { 10 | // 应该不会发生这样情况,仅保证程序正确性 11 | window.showErrorMessage(t(["workspace is empty", "工作空间为空"])); 12 | return; 13 | } 14 | const workspaceRoot = workspaceFolders[0]; 15 | const docUri = joinPath(workspaceRoot.uri, FILE_NAME); 16 | 17 | initializePreferenceCommands(context); 18 | new Live(context).initialize(docUri).catch((err) => { 19 | logError(err); 20 | window.showErrorMessage( 21 | t([ 22 | "initialize error, please refresh and retry again", 23 | "初始化错误,请刷新浏览器重试", 24 | ]) 25 | ); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /extensions/collaboration/src/ot/README.md: -------------------------------------------------------------------------------- 1 | 参照 https://github.com/Operational-Transformation/ot.js 2 | 3 | fork 到本地实现 ts 版,便于调试和分析 OT 4 | -------------------------------------------------------------------------------- /extensions/collaboration/src/ot/client.ts: -------------------------------------------------------------------------------- 1 | // translation of https://github.com/djspiewak/cccp/blob/master/agent/src/main/scala/com/codecommit/cccp/agent/state.scala 2 | /* eslint-disable react/no-access-state-in-setstate */ 3 | 4 | import { TextOperation } from './text-operation'; 5 | 6 | interface IState { 7 | name: string; 8 | applyClient(client: Client, operation: TextOperation): IState; 9 | applyServer(client: Client, operation: TextOperation): IState; 10 | serverAck(client: Client): IState; 11 | transformSelection(selection: T): T; 12 | resend?(client: Client): void; 13 | } 14 | 15 | // In the 'Synchronized' state, there is no pending operation that the client 16 | // has sent to the server. 17 | export class Synchronized implements IState { 18 | name = 'Synchronized'; 19 | 20 | applyClient(client: Client, operation: TextOperation) { 21 | // When the user makes an edit, send the operation to the server and 22 | // switch to the 'AwaitingConfirm' state 23 | client.sendOperation(client.revision, operation); 24 | return new AwaitingConfirm(operation); 25 | } 26 | 27 | applyServer(client: Client, operation: TextOperation) { 28 | // When we receive a new operation from the server, the operation can be 29 | // simply applied to the current document 30 | client.applyOperation(operation); 31 | return this; 32 | } 33 | 34 | serverAck(client: Client): never { 35 | throw new Error('There is no pending operation.'); 36 | } 37 | 38 | // Nothing to do because the latest server state and client state are the same. 39 | transformSelection(x) { 40 | return x; 41 | } 42 | } 43 | 44 | // In the 'AwaitingConfirm' state, there's one operation the client has sent 45 | // to the server and is still waiting for an acknowledgement. 46 | export class AwaitingConfirm implements IState { 47 | outstanding: TextOperation; 48 | name = 'AwaitingConfirm'; 49 | 50 | constructor(outstanding: TextOperation) { 51 | // Save the pending operation 52 | this.outstanding = outstanding; 53 | } 54 | 55 | applyClient(client: Client, operation: TextOperation) { 56 | // When the user makes an edit, don't send the operation immediately, 57 | // instead switch to 'AwaitingWithBuffer' state 58 | return new AwaitingWithBuffer(this.outstanding, operation); 59 | } 60 | 61 | applyServer(client: Client, operation: TextOperation) { 62 | // This is another client's operation. Visualization: 63 | // 64 | // /\ 65 | // this.outstanding / \ operation 66 | // / \ 67 | // \ / 68 | // pair[1] \ / pair[0] (new outstanding) 69 | // (can be applied \/ 70 | // to the client's 71 | // current document) 72 | const pair = TextOperation.transform(this.outstanding, operation); 73 | client.applyOperation(pair[1]); 74 | return new AwaitingConfirm(pair[0]); 75 | } 76 | 77 | serverAck(client: Client) { 78 | // The client's operation has been acknowledged 79 | // => switch to synchronized state 80 | return synchronized_; 81 | } 82 | 83 | transformSelection(selection: any) { 84 | return selection.transform(this.outstanding); 85 | } 86 | 87 | resend(client: Client) { 88 | // The confirm didn't come because the client was disconnected. 89 | // Now that it has reconnected, we resend the outstanding operation. 90 | client.sendOperation(client.revision, this.outstanding); 91 | } 92 | } 93 | 94 | // In the 'AwaitingWithBuffer' state, the client is waiting for an operation 95 | // to be acknowledged by the server while buffering the edits the user makes 96 | export class AwaitingWithBuffer implements IState { 97 | outstanding: TextOperation; 98 | buffer: TextOperation; 99 | 100 | name = 'AwaitingWithBuffer'; 101 | 102 | constructor(outstanding: TextOperation, buffer: TextOperation) { 103 | // Save the pending operation and the user's edits since then 104 | this.outstanding = outstanding; 105 | this.buffer = buffer; 106 | } 107 | 108 | applyClient(client: Client, operation: TextOperation) { 109 | // Compose the user's changes onto the buffer 110 | const newBuffer = this.buffer.compose(operation); 111 | return new AwaitingWithBuffer(this.outstanding, newBuffer); 112 | } 113 | 114 | applyServer(client: Client, operation: TextOperation) { 115 | // Operation comes from another client 116 | // 117 | // /\ 118 | // this.outstanding / \ operation 119 | // / \ 120 | // /\ / 121 | // this.buffer / \* / pair1[0] (new outstanding) 122 | // / \/ 123 | // \ / 124 | // pair2[1] \ / pair2[0] (new buffer) 125 | // the transformed \/ 126 | // operation -- can 127 | // be applied to the 128 | // client's current 129 | // document 130 | // 131 | // * pair1[1] 132 | const transform = TextOperation.transform; 133 | const pair1 = transform(this.outstanding, operation); 134 | const pair2 = transform(this.buffer, pair1[1]); 135 | client.applyOperation(pair2[1]); 136 | return new AwaitingWithBuffer(pair1[0], pair2[0]); 137 | } 138 | 139 | serverAck(client: Client) { 140 | // The pending operation has been acknowledged 141 | // => send buffer 142 | client.sendOperation(client.revision, this.buffer); 143 | return new AwaitingConfirm(this.buffer); 144 | } 145 | 146 | transformSelection(selection: any) { 147 | return selection.transform(this.outstanding).transform(this.buffer); 148 | } 149 | 150 | resend(client: Client) { 151 | // The confirm didn't come because the client was disconnected. 152 | // Now that it has reconnected, we resend the outstanding operation. 153 | client.sendOperation(client.revision, this.outstanding); 154 | } 155 | } 156 | 157 | // Singleton 158 | export const synchronized_ = new Synchronized(); 159 | 160 | export abstract class Client { 161 | revision: number; 162 | state: IState; 163 | 164 | constructor(revision: number) { 165 | this.revision = revision; 166 | this.state = synchronized_; 167 | } 168 | 169 | setState(state: IState) { 170 | this.state = state; 171 | } 172 | 173 | // Call this method when the user changes the document. 174 | applyClient(operation: TextOperation) { 175 | this.setState(this.state.applyClient(this, operation)); 176 | } 177 | 178 | // Call this method with a new operation from the server 179 | applyServer(operation: TextOperation) { 180 | this.revision++; 181 | this.setState(this.state.applyServer(this, operation)); 182 | } 183 | 184 | serverAck() { 185 | this.revision++; 186 | this.setState(this.state.serverAck(this)); 187 | } 188 | 189 | serverReconnect() { 190 | if (typeof this.state.resend === 'function') { 191 | this.state.resend(this); 192 | } 193 | } 194 | 195 | // Transforms a selection from the latest known server state to the current 196 | // client state. For example, if we get from the server the information that 197 | // another user's cursor is at position 3, but the server hasn't yet received 198 | // our newest operation, an insertion of 5 characters at the beginning of the 199 | // document, the correct position of the other user's cursor in our current 200 | // document is 8. 201 | transformSelection(selection) { 202 | return this.state.transformSelection(selection); 203 | } 204 | 205 | abstract sendOperation(revision: number, operation: TextOperation): void; 206 | 207 | abstract applyOperation(operation: TextOperation): void; 208 | } 209 | -------------------------------------------------------------------------------- /extensions/collaboration/src/ot/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | export * from './text-operation'; 3 | -------------------------------------------------------------------------------- /extensions/collaboration/src/preference.ts: -------------------------------------------------------------------------------- 1 | import { commands, workspace, ExtensionContext } from 'vscode'; 2 | import { PREFERENCE_COMMAND, PreferenceConfig } from './types'; 3 | 4 | export const initializePreferenceCommands = (context: ExtensionContext) => { 5 | context.subscriptions.push( 6 | commands.registerCommand(PREFERENCE_COMMAND.Preference, (list: PreferenceConfig) => { 7 | list.forEach(([type, value]) => { 8 | if (type === 'fontSize') { 9 | workspace.getConfiguration().update('editor.fontSize', value, true); 10 | } else if (type === 'tabSize') { 11 | workspace.getConfiguration().update('editor.tabSize', value, true); 12 | } else if (type === 'theme') { 13 | const themeType = value === 'dark' ? 'opensumi-design-dark-theme' : 'opensumi-design-light-theme'; 14 | workspace.getConfiguration().update('general.theme', themeType, true); 15 | } 16 | }); 17 | }) 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /extensions/collaboration/src/types.ts: -------------------------------------------------------------------------------- 1 | import { Range } from 'vscode'; 2 | import { SerializedTextOperation } from './ot'; 3 | 4 | export interface UserId { 5 | userId: number; 6 | } 7 | 8 | export interface UserProfile extends UserId { 9 | name: string; 10 | } 11 | 12 | export interface UserSelection { 13 | range: Range; 14 | isReversed: boolean; 15 | } 16 | 17 | export interface DocState { 18 | revision: number; 19 | code: string; 20 | mode?: string; 21 | } 22 | 23 | export type InitializeState = 24 | | { 25 | status: 'success'; 26 | data: UserProfile & DocState; 27 | } 28 | | { 29 | status: 'fail'; 30 | }; 31 | 32 | export interface SendOperationType { 33 | ops: SerializedTextOperation; 34 | revision: number; 35 | } 36 | 37 | export interface SendSelectionType { 38 | selection: Array<{ 39 | offset: [number, number]; 40 | isReversed: boolean; 41 | }>; 42 | } 43 | 44 | export interface ApplyOperationType extends SendOperationType, UserId {} 45 | 46 | export interface ApplySelectionType extends SendSelectionType, UserId {} 47 | 48 | export enum LIVE_COMMAND { 49 | Reconnect = 'collaboration.reconnect', 50 | Initialize = 'collaboration.initialize', 51 | Initialized = 'collaboration.initialized', 52 | SendOperation = 'collaboration.sendOperation', 53 | ApplyOperation = 'collaboration.applyOperation', 54 | SendSelection = 'collaboration.sendSelection', 55 | ApplySelection = 'collaboration.applySelection', 56 | Join = 'collaboration.join', 57 | Leave = 'collaboration.level', 58 | SyncState = 'collaboration.syncState', 59 | UpdateUser = 'collaboration.updateUser', 60 | } 61 | 62 | export enum PREFERENCE_COMMAND { 63 | Language = 'alitcode.setLanguage', 64 | Preference = 'alitcode.preference', 65 | } 66 | 67 | export enum COMMON_COMMAND { 68 | Report = 'alitcode.report', 69 | } 70 | 71 | export type PreferenceType = 'fontSize' | 'tabSize' | 'theme'; 72 | 73 | export type PreferenceConfig = [PreferenceType, any][]; 74 | 75 | export enum REPORT_NAME { 76 | SyncState = 'syncState', 77 | SyncStateFail = 'syncStateFail', 78 | } 79 | -------------------------------------------------------------------------------- /extensions/collaboration/src/utils.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Uri, TextDocument, Selection, workspace, commands } from 'vscode'; 7 | import { COMMON_COMMAND } from './types'; 8 | 9 | export const FILE_NAME = 'code'; 10 | 11 | export function getScheme(uri: string) { 12 | return uri.substr(0, uri.indexOf(':')); 13 | } 14 | 15 | export function dirname(uri: string) { 16 | const lastIndexOfSlash = uri.lastIndexOf('/'); 17 | return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : ''; 18 | } 19 | 20 | export function basename(uri: string) { 21 | const lastIndexOfSlash = uri.lastIndexOf('/'); 22 | return uri.substr(lastIndexOfSlash + 1); 23 | } 24 | 25 | const Slash = '/'.charCodeAt(0); 26 | const Dot = '.'.charCodeAt(0); 27 | 28 | export function isAbsolutePath(path: string) { 29 | return path.charCodeAt(0) === Slash; 30 | } 31 | 32 | export function resolvePath(uri: Uri, path: string): Uri { 33 | if (isAbsolutePath(path)) { 34 | return uri.with({ path: normalizePath(path.split('/')) }); 35 | } 36 | return joinPath(uri, path); 37 | } 38 | 39 | export function normalizePath(parts: string[]): string { 40 | const newParts: string[] = []; 41 | for (const part of parts) { 42 | if (part.length === 0 || (part.length === 1 && part.charCodeAt(0) === Dot)) { 43 | // ignore 44 | } else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) { 45 | newParts.pop(); 46 | } else { 47 | newParts.push(part); 48 | } 49 | } 50 | if (parts.length > 1 && parts[parts.length - 1].length === 0) { 51 | newParts.push(''); 52 | } 53 | let res = newParts.join('/'); 54 | if (parts[0].length === 0) { 55 | res = '/' + res; 56 | } 57 | return res; 58 | } 59 | 60 | export function joinPath(uri: Uri, ...paths: string[]): Uri { 61 | const parts = uri.path.split('/'); 62 | for (let path of paths) { 63 | parts.push(...path.split('/')); 64 | } 65 | return uri.with({ path: normalizePath(parts) }); 66 | } 67 | 68 | export function getSelection(doc: TextDocument, selection: Selection) { 69 | const startSelection = doc.offsetAt(selection.anchor); 70 | const endSelection = doc.offsetAt(selection.end); 71 | 72 | return { 73 | selection: startSelection === endSelection ? [] : [startSelection, endSelection], 74 | cursorPosition: endSelection, 75 | }; 76 | } 77 | 78 | let isDebug: boolean | null = null; 79 | 80 | export const log = (...args: any) => { 81 | if (isDebug === null) { 82 | isDebug = workspace.getConfiguration().get('alitcode.debug') || false; 83 | } 84 | if (isDebug) { 85 | console.log('[alitcode]: ', ...args); 86 | } 87 | }; 88 | 89 | export const logWarn = (...args: any) => { 90 | console.warn('[alitcode][WARN]: ', ...args); 91 | }; 92 | 93 | export const logError = (...args: any) => { 94 | if (isDebug === null) { 95 | isDebug = workspace.getConfiguration().get('alitcode.debug') || false; 96 | } 97 | if (isDebug) { 98 | console.error('[alitcode][ERROR]: ', ...args); 99 | } 100 | }; 101 | 102 | let locale: 'zh-CN' | 'en-US'; 103 | export const t = (message: [string, string]) => { 104 | if (!locale) { 105 | locale = workspace.getConfiguration().get('alitcode.locale') || 'zh-CN'; 106 | } 107 | return locale === 'en-US' ? message[0] : message[1]; 108 | }; 109 | 110 | export const report = (name: string, msg?: string, extra?: any) => { 111 | commands.executeCommand(COMMON_COMMAND.Report, name, msg, extra).then(undefined, (err) => { 112 | logError(err); 113 | }); 114 | }; 115 | -------------------------------------------------------------------------------- /extensions/collaboration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2015", 4 | "target": "ES2015", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "strictPropertyInitialization": false, 8 | "sourceMap": true, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "importHelpers": true, 12 | "resolveJsonModule": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "downlevelIteration": true, 16 | "noEmitOnError": false, 17 | "noImplicitAny": false, 18 | "skipLibCheck": false, 19 | "strictFunctionTypes": false, 20 | "jsx": "react", 21 | "baseUrl": ".", 22 | "rootDir": ".", 23 | "outDir": "lib", 24 | "noUnusedLocals": false, 25 | "allowSyntheticDefaultImports": true, 26 | "esModuleInterop": true, 27 | "declaration": true, 28 | "declarationMap": true, 29 | "allowJs": true, 30 | "lib": [ 31 | "DOM", 32 | "ES2015" 33 | ], 34 | "typeRoots": [ 35 | "./node_modules/@types" 36 | ], 37 | "types": [ 38 | "./node_modules/@ali/kaitian" 39 | ], 40 | "paths": { 41 | "vscode": [ 42 | "./node_modules/@opensumi/sumi" 43 | ], 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /extensions/collaboration/webpack.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const path = require('path'); 4 | 5 | const outputPath = path.join(__dirname, 'out'); 6 | 7 | /** 8 | * @type {import('webpack').Configuration} 9 | */ 10 | module.exports = { 11 | entry: path.join(__dirname, 'src/extension'), 12 | output: { 13 | filename: 'extension.js', 14 | path: outputPath, 15 | libraryTarget: 'commonjs2', 16 | }, 17 | devtool: false, 18 | mode: 'development', 19 | resolve: { 20 | extensions: ['.ts', '.tsx', '.js', '.json'], 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.tsx?$/, 26 | use: [ 27 | { 28 | loader: 'ts-loader', 29 | options: { 30 | transpileOnly: true, 31 | }, 32 | }, 33 | ], 34 | }, 35 | ], 36 | }, 37 | plugins: [], 38 | target: 'webworker', 39 | externals: { 40 | vscode: 'commonjs vscode', 41 | kaitian: 'commonjs kaitian', 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /extensions/worker-example/browser.js: -------------------------------------------------------------------------------- 1 | const React = require('React'); 2 | 3 | const Component = () => { 4 | return React.createElement('div', {}, `当前时间戳:${Date.now()}`); 5 | }; 6 | 7 | exports.Component = Component; 8 | -------------------------------------------------------------------------------- /extensions/worker-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worker-example", 3 | "version": "0.0.3", 4 | "publisher": "codeblitz", 5 | "engines": { 6 | "kaitian": "*" 7 | }, 8 | "activationEvents": [ 9 | "*" 10 | ], 11 | "enableKaitianWebAssets": true, 12 | "contributes": { 13 | "commands": [ 14 | { 15 | "command": "test.registerMenu", 16 | "title": "测试注册菜单", 17 | "icon": "$(check)", 18 | "category": "Test" 19 | } 20 | ], 21 | "menus": { 22 | "editor/title": [ 23 | { 24 | "command": "test.registerMenu", 25 | "group": "navigation" 26 | } 27 | ] 28 | } 29 | }, 30 | "kaitianContributes": { 31 | "workerMain": "./worker.js", 32 | "browserMain": "./browser.js", 33 | "browserViews": { 34 | "left": { 35 | "type": "add", 36 | "view": [ 37 | { 38 | "id": "Component", 39 | "icon": "extension" 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /extensions/worker-example/worker-example.d.ts: -------------------------------------------------------------------------------- 1 | import { metadata } from "@codeblitzjs/ide-core/extensions/_reference"; 2 | export = metadata; 3 | -------------------------------------------------------------------------------- /extensions/worker-example/worker-example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "codeblitz", 4 | "name": "worker-example", 5 | "version": "0.0.6" 6 | }, 7 | "packageJSON": { 8 | "name": "worker-example", 9 | "version": "0.0.3", 10 | "publisher": "codeblitz", 11 | "engines": { 12 | "kaitian": "*" 13 | }, 14 | "activationEvents": [ 15 | "*" 16 | ], 17 | "enableKaitianWebAssets": true, 18 | "contributes": { 19 | "commands": [ 20 | { 21 | "command": "test.registerMenu", 22 | "title": "测试注册菜单", 23 | "icon": "$(check)", 24 | "category": "Test" 25 | } 26 | ], 27 | "menus": { 28 | "editor/title": [ 29 | { 30 | "command": "test.registerMenu", 31 | "group": "navigation" 32 | } 33 | ] 34 | } 35 | }, 36 | "kaitianContributes": { 37 | "workerMain": "./worker.js", 38 | "browserMain": "./browser.js", 39 | "browserViews": { 40 | "left": { 41 | "type": "add", 42 | "view": [ 43 | { 44 | "id": "Component", 45 | "icon": "extension" 46 | } 47 | ] 48 | } 49 | } 50 | } 51 | } 52 | , 53 | "pkgNlsJSON": {}, 54 | "nlsList": [], 55 | "extendConfig": {}, 56 | "webAssets": [], 57 | "mode": "public" 58 | } 59 | -------------------------------------------------------------------------------- /extensions/worker-example/worker.js: -------------------------------------------------------------------------------- 1 | const { commands } = require('vscode'); 2 | const log = (scope, ...msg) => console.log(`>>>[worker-example][${scope}]`, ...msg); 3 | 4 | /** @type {import('vscode').ExtensionContext} */ 5 | let context; 6 | 7 | exports.activate = async (ctx) => { 8 | context = ctx; 9 | 10 | log('start'); 11 | 12 | // 注册菜单的命令 13 | commands.registerCommand('test.registerMenu', () => { 14 | log('执行注册菜单命令') 15 | }) 16 | 17 | commands.registerCommand('plugin.command.test', async () => { 18 | commands.registerCommand('plugin.command.say', (msg) => { 19 | log('plugin', msg); 20 | }); 21 | log('plugin', await commands.executeCommand('plugin.command.add', 1)); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /filesystem.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo } from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | import { AppRenderer, BrowserFSFileType as FileType, IAppRendererProps, Uri , requireModule} from '@codeblitzjs/ide-core'; 5 | import '@codeblitzjs/ide-core/bundle/codeblitz.css'; 6 | import '@codeblitzjs/ide-core/languages'; 7 | import Select from 'antd/lib/select'; 8 | import 'antd/lib/select/style/index.css'; 9 | import { Button } from 'antd'; 10 | 11 | const path = requireModule('path'); 12 | const fse = requireModule('fs-extra'); 13 | 14 | const dirMap: Record = { 15 | '/': [ 16 | ['lib', FileType.DIRECTORY], 17 | ['Readme.md', FileType.FILE], 18 | ['LICENSE', FileType.FILE], 19 | ['package.json', FileType.FILE], 20 | ], 21 | '/lib': [ 22 | ['application.js', FileType.FILE], 23 | ['context.js', FileType.FILE], 24 | ['request.js', FileType.FILE], 25 | ['response.js', FileType.FILE], 26 | ], 27 | }; 28 | 29 | const FileService = requireModule('@opensumi/ide-file-service'); 30 | const { IFileServiceClient } = FileService; 31 | 32 | let zipData: Buffer; 33 | 34 | const zipDataPromise = (async () => { 35 | const res = await fetch( 36 | 'http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/green-trail-test/dc85f34d-2467-436b-a0fe-092133ead0d6/demo.zip' 37 | ); 38 | const buf = await res.arrayBuffer(); 39 | zipData = Buffer.from(new Uint8Array(buf)); 40 | })(); 41 | 42 | const workspaceDir = 'filesystem'; 43 | 44 | const App = () => { 45 | const [fsType, setFsType] = useState('FileIndexSystem'); 46 | 47 | const filesystem = useMemo< 48 | NonNullable['filesystem'] | undefined 49 | >(() => { 50 | switch (fsType) { 51 | case 'IndexedDB': 52 | return { 53 | fs: 'IndexedDB', 54 | options: { 55 | storeName: 'my_db', 56 | }, 57 | }; 58 | case 'InMemory': 59 | return { 60 | fs: 'InMemory', 61 | }; 62 | case 'FileIndexSystem': 63 | return { 64 | fs: 'FileIndexSystem', 65 | options: { 66 | // 初始全量文件索引 67 | requestFileIndex() { 68 | return Promise.resolve({ 69 | 'main.html': '
', 70 | 'main.css': 'body {}', 71 | 'main.js': 'console.log("main")', 72 | 'package.json': '{\n "name": "Codeblitz"\n}', 73 | 'readme.md': '# Codeblitz', 74 | }); 75 | }, 76 | }, 77 | }; 78 | case 'DynamicRequest': 79 | return { 80 | fs: 'DynamicRequest', 81 | options: { 82 | readDirectory(p: string) { 83 | return dirMap[p]; 84 | }, 85 | async readFile(p) { 86 | const res = await fetch( 87 | `http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/green-trail-test/a87fb80d-3028-4b19-93a9-2da6f871f369/koa${p}` 88 | ); 89 | return Buffer.from(await res.arrayBuffer()); 90 | }, 91 | }, 92 | }; 93 | case 'ZipFS': 94 | return { 95 | fs: 'ZipFS', 96 | options: { 97 | zipData, 98 | }, 99 | }; 100 | case 'FolderAdapter': 101 | return { 102 | fs: 'FolderAdapter', 103 | options: { 104 | folder: '/demo', 105 | wrapped: { 106 | fs: 'ZipFS', 107 | options: { 108 | zipData, 109 | }, 110 | }, 111 | }, 112 | }; 113 | case 'OverlayFS': 114 | return { 115 | fs: 'OverlayFS', 116 | options: { 117 | writable: { fs: 'InMemory' }, 118 | readable: { 119 | fs: 'ZipFS', 120 | options: { 121 | zipData, 122 | }, 123 | }, 124 | }, 125 | }; 126 | } 127 | }, [fsType]); 128 | 129 | const workspace = filesystem ? { filesystem } : undefined; 130 | 131 | return ( 132 |
133 |
134 | onChange={(e) => setFsType(e)} style={{ width: 200 }} value={fsType}> 135 | IndexedDB 136 | InMemory 137 | FileIndexSystem 138 | DynamicRequest 139 | ZipFS 140 | FolderAdapter 141 | OverlayFS 142 | 143 | 144 | { 145 | fsType === 'FileIndexSystem' && ( 146 | 154 | ) 155 | } 156 |
157 | 158 |
159 | { 162 | window.app = app; 163 | const fileService = app.injector.get(IFileServiceClient) 164 | fileService.onFilesChanged(async e => { 165 | // 获取新建的文件 166 | const createFiles = e.filter(f => { 167 | // FileChangeType 168 | // UPDATED = 0, 169 | // ADDED = 1, 170 | // DELETED = 2 171 | if(f.type ===1){ 172 | return f; 173 | } 174 | }) 175 | const promiseAll: Promise[] = []; 176 | createFiles.map( file => { 177 | const uri = file.uri; 178 | promiseAll.push(fileService.readFile(uri)); 179 | }) 180 | 181 | await Promise.all(promiseAll).then(res => { 182 | res.map(r => { 183 | console.log(r.content.toString()) 184 | }) 185 | }) 186 | }); 187 | }} 188 | appConfig={{ 189 | workspaceDir, 190 | defaultPreferences: { 191 | 'general.theme': 'opensumi-design-light-theme', 192 | }, 193 | }} 194 | runtimeConfig={{ 195 | workspace, 196 | }} 197 | /> 198 |
199 |
200 | ); 201 | }; 202 | 203 | zipDataPromise.then(() => { 204 | ReactDOM.createRoot(document.getElementById('main')!).render(); 205 | }); 206 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Codeblitz 6 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import Button from 'antd/lib/button'; 4 | import 'antd/lib/button/style/index.css'; 5 | 6 | //#region codeblitz 7 | import { AppRenderer, SlotLocation, SlotRenderer, SplitPanel, BoxPanel, IAppInstance } from '@codeblitzjs/ide-core' 8 | import '@codeblitzjs/ide-core/bundle/codeblitz.css'; 9 | //#endregion 10 | 11 | //#region 语法高亮 12 | import '@codeblitzjs/ide-core/languages/html'; 13 | import '@codeblitzjs/ide-core/languages/handlebars'; 14 | import '@codeblitzjs/ide-core/languages/css'; 15 | import '@codeblitzjs/ide-core/languages/scss'; 16 | import '@codeblitzjs/ide-core/languages/less'; 17 | import '@codeblitzjs/ide-core/languages/javascript'; 18 | import '@codeblitzjs/ide-core/languages/typescript'; 19 | import '@codeblitzjs/ide-core/languages/json'; 20 | //#endregion 21 | 22 | //#region 语言功能 23 | import html from '@codeblitzjs/ide-core/extensions/codeblitz.html-language-features-worker'; 24 | import css from '@codeblitzjs/ide-core/extensions/codeblitz.css-language-features-worker'; 25 | import typescript from '@codeblitzjs/ide-core/extensions/codeblitz.typescript-language-features-worker'; 26 | import json from '@codeblitzjs/ide-core/extensions/codeblitz.json-language-features-worker'; 27 | import WorkerExample from './extensions/worker-example/worker-example'; 28 | //#endregion 29 | 30 | //#region 获取内置模块,提供 IDE 层面的控制能力 31 | import { IEditorDocumentModelService } from '@codeblitzjs/ide-core/modules/ide-editor' 32 | import { CommandService, EDITOR_COMMANDS, URI } from '@codeblitzjs/ide-core/modules/ide-core-browser' 33 | //#endregion 34 | 35 | // 布局配置,可根据需要增删模块 36 | export const layoutConfig = { 37 | [SlotLocation.left]: { 38 | modules: ['@opensumi/ide-explorer'], 39 | }, 40 | [SlotLocation.main]: { 41 | modules: ['@opensumi/ide-editor'], 42 | }, 43 | // [SlotLocation.bottom]: { 44 | // modules: ['@opensumi/ide-output', '@opensumi/ide-markers'], 45 | // }, 46 | [SlotLocation.statusBar]: { 47 | modules: ['@opensumi/ide-status-bar'], 48 | }, 49 | }; 50 | 51 | // 界面布局组件,可根据需要调整 52 | const LayoutComponent = () => ( 53 | 54 | 55 | 56 | 57 | 58 | {/* */} 59 | 60 | 61 | 62 | 63 | ); 64 | 65 | const App: React.FC = () => { 66 | const [key, setKey] = React.useState(0); 67 | const app = React.useRef(null) 68 | 69 | const update = () => { 70 | if (!app.current) return 71 | 72 | const docModelService: IEditorDocumentModelService = app.current.injector.get(IEditorDocumentModelService) 73 | const workspaceUri = URI.file(app.current.config.workspaceDir); 74 | Promise.all([{ 75 | filepath: 'main.html', 76 | content: '
' 77 | }, { 78 | filepath: 'main.js', 79 | content: 'console.log("main")' 80 | }].map(item => { 81 | docModelService.createModelReference(workspaceUri.resolve(item.filepath)).then((ref) => { 82 | ref.instance.updateContent(item.content) 83 | }) 84 | })) 85 | } 86 | 87 | const getDirty = () => { 88 | if (!app.current) return 89 | 90 | const docModelService: IEditorDocumentModelService = app.current.injector.get(IEditorDocumentModelService) 91 | const modelList = docModelService.getAllModels() 92 | .filter(model => model.uri.codeUri.path.startsWith(app.current!.config.workspaceDir) && model.dirty) 93 | 94 | console.log('modelList', modelList) 95 | } 96 | 97 | const saveAll = () => { 98 | if (!app.current) return 99 | 100 | const commandService: CommandService = app.current.injector.get(CommandService) 101 | commandService.executeCommand(EDITOR_COMMANDS.SAVE_ALL.id) 102 | } 103 | 104 | return ( 105 |
106 |
107 | 108 | 109 | 110 | 111 |
112 |
113 | { 116 | app.current = _app 117 | }} 118 | appConfig={{ 119 | // 工作空间目录,最好确保不同项目名称不同,如 group/repository 的形式,工作空间目录会挂载到 /workspace 根目录下 120 | workspaceDir: 'playground', 121 | layoutConfig, 122 | layoutComponent: LayoutComponent, 123 | // 默认偏好设置 124 | defaultPreferences: { 125 | // 主题色 opensumi-design-light-theme | opensumi-design-dark-theme 126 | 'general.theme': 'opensumi-design-light-theme', 127 | 'editor.previewMode': false, 128 | // 'editor.forceReadOnly': true, 129 | 130 | // 'editor.readonlyFiles': ['/workspace/**'] 131 | }, 132 | // 左侧面板默认宽度 133 | panelSizes: { 134 | [SlotLocation.left]: 220, 135 | }, 136 | // 扩展 137 | extensionMetadata: [html, css, typescript, json, WorkerExample] 138 | }} 139 | runtimeConfig={{ 140 | // 禁止就改文件树,此时无法新增、删除、重命名文件 141 | disableModifyFileTree: true, 142 | // 默认打开文件 143 | defaultOpenFile: 'main.js', 144 | workspace: { 145 | // 文件系统配置 146 | filesystem: { 147 | fs: 'FileIndexSystem', 148 | options: { 149 | // 初始全量文件索引 150 | requestFileIndex() { 151 | return Promise.resolve({ 152 | 'main.html': '
', 153 | 'main.css': 'body {}', 154 | 'main.js': 'console.log("main")', 155 | 'package.json': '{\n "name": "Riddle"\n}', 156 | }); 157 | }, 158 | }, 159 | }, 160 | // 文件保存事件 161 | onDidSaveTextDocument(e) { 162 | console.log('>>>save', e) 163 | }, 164 | onDidChangeTextDocument(e) { 165 | console.log('>>>change', e) 166 | } 167 | }, 168 | // 隐藏左侧 tabbar 169 | hideLeftTabBar: true, 170 | // 注销左侧下方的 bar,此时设置按钮会被隐藏 171 | unregisterActivityBarExtra: true 172 | }} 173 | /> 174 |
175 |
176 | ); 177 | }; 178 | 179 | ReactDOM.createRoot(document.getElementById('main')!).render(); 180 | -------------------------------------------------------------------------------- /merge-editor/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | import { AppRenderer, BrowserFSFileType as FileType } from '@codeblitzjs/ide-core' 5 | import '@codeblitzjs/ide-core/bundle/codeblitz.css'; 6 | import '@codeblitzjs/ide-core/languages' 7 | 8 | import mergeConflict from '@codeblitzjs/ide-core/extensions/codeblitz.merge-conflict'; 9 | import { MergeEditorModule } from './merge-editor.module'; 10 | import { workspaceDir } from './util'; 11 | import { ancestorContent, input1Content, input2Content } from './mock/conflicts.mock'; 12 | 13 | const dirMap: Record = { 14 | '/': [ 15 | ['merge-editor', FileType.DIRECTORY], 16 | ['doc', FileType.DIRECTORY], 17 | ['hello.html', FileType.FILE], 18 | ['hello.js', FileType.FILE], 19 | ['hello.py', FileType.FILE], 20 | ['Hello.java', FileType.FILE], 21 | ['hello.go', FileType.FILE], 22 | ['appveyor.yml', FileType.FILE], 23 | ['test.yaml', FileType.FILE], 24 | 25 | ], 26 | '/doc': [ 27 | ['README.md', FileType.FILE], 28 | ], 29 | '/merge-editor': [ 30 | ['ancestor.mock.js', FileType.FILE], 31 | ['input1.mock.js', FileType.FILE], 32 | ['input2.mock.js', FileType.FILE], 33 | ] 34 | }; 35 | 36 | const fileMap = { 37 | '/doc/README.md': '# codeblitz-startup\n> codeblitz demo', 38 | '/merge-editor/ancestor.mock.js': ancestorContent, 39 | '/merge-editor/input1.mock.js': input1Content, 40 | '/merge-editor/input2.mock.js': input2Content, 41 | '/hello.html': ` 42 | 43 | 44 | 45 |

Hello, World!

46 | 47 | 48 | `.trimStart(), 49 | '/hello.js': 'console.log("Hello, World!");', 50 | '/hello.py': 'print("Hello, World!")', 51 | '/Hello.java': ` 52 | public class Hello { 53 | public static void main(String[] args) { 54 | System.out.println("Hello, World!"); 55 | } 56 | } 57 | `.trimStart(), 58 | '/hello.go': ` 59 | package main 60 | 61 | import "fmt" 62 | 63 | func main() { 64 | fmt.Println("Hello, World!") 65 | } 66 | `.trimStart(), 67 | '/appveyor.yml':`# version format 68 | version: 1.0.{build} 69 | 70 | # you can use {branch} name in version format too 71 | # version: 1.0.{build}-{branch} 72 | 73 | # branches to build 74 | branches: 75 | # whitelist 76 | only: 77 | - master 78 | - production 79 | 80 | # blacklist 81 | except: 82 | - gh-pages 83 | 84 | # Do not build on tags (GitHub, Bitbucket, GitLab, Gitea) 85 | skip_tags: true 86 | 87 | # Start builds on tags only (GitHub, BitBucket, GitLab, Gitea) 88 | skip_non_tags: true`.trimStart() 89 | } 90 | 91 | const App = () => { 92 | return ( 93 | 122 | ) 123 | } 124 | 125 | ReactDOM.createRoot(document.getElementById('main')!).render(); 126 | -------------------------------------------------------------------------------- /merge-editor/merge-editor.module.ts: -------------------------------------------------------------------------------- 1 | import { Provider, Injectable, Autowired } from '@opensumi/di'; 2 | import { 3 | Domain, 4 | BrowserModule, 5 | CommandContribution, 6 | CommandRegistry, 7 | getLanguageId, 8 | Disposable, 9 | CommandService, 10 | Uri, 11 | URI, 12 | getIcon, 13 | } from '@opensumi/ide-core-browser'; 14 | import { CodeModelService } from '@codeblitzjs/ide-code-service'; 15 | import { SCHEME, toMergeUris, workspaceDir } from './util'; 16 | import { WORKSPACE_ROOT } from '@codeblitzjs/ide-sumi-core'; 17 | 18 | @Domain(CommandContribution) 19 | export class MergeEditorContribution extends Disposable implements CommandContribution { 20 | @Autowired() 21 | codeModel: CodeModelService; 22 | 23 | @Autowired(CommandService) 24 | commandService: CommandService; 25 | 26 | registerCommands(commands: CommandRegistry): void { 27 | this.addDispose([ 28 | commands.registerCommand( 29 | { id: 'merge-editor.resolve-conflicts', label: '合并解决冲突' }, 30 | { 31 | execute: async () => { 32 | 33 | const workspaceUri = URI.file(`${WORKSPACE_ROOT}/${workspaceDir}`); 34 | const mockUri = workspaceUri.resolve('merge-editor/ancestor.mock.js'); 35 | 36 | type InputData = { uri: Uri; title?: string; detail?: string; description?: string }; 37 | const mergeUris = toMergeUris(mockUri.codeUri); 38 | 39 | const current: InputData = { uri: mergeUris.ours, title: 'Current' }; 40 | const incoming: InputData = { uri: mergeUris.theirs, title: 'Incoming' }; 41 | 42 | const options = { 43 | ancestor: mergeUris.base, 44 | input1: current, 45 | input2: incoming, 46 | output: mockUri.codeUri 47 | }; 48 | 49 | await this.commandService.executeCommand( 50 | '_open.mergeEditor', 51 | options 52 | ); 53 | }, 54 | } 55 | ), 56 | ]); 57 | } 58 | } 59 | 60 | @Injectable() 61 | export class MergeEditorModule extends BrowserModule { 62 | providers: Provider[] = [MergeEditorContribution]; 63 | } 64 | -------------------------------------------------------------------------------- /merge-editor/mock/conflicts.mock.ts: -------------------------------------------------------------------------------- 1 | export const ancestorContent = ` 2 | module.exports = { 3 | preset: 'ts-jest', 4 | coverageDirectory: 'coverage', 5 | coverageReporters: ['html', 'lcov', 'text'], 6 | collectCoverageFrom: [ 7 | 'packages/*/src/**/*.ts', 8 | '!packages/toolkit/**', 9 | '!packages/core/.kaitian/**', 10 | '!packages/core/extensions/**', 11 | ], 12 | watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'], 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], 14 | moduleNameMapper: { 15 | '^@codeblitzjs/ide-core-(?!browserfs)(.*?)$': '/packages/$1/src', 16 | '^@codeblitzjs/ide-core$': '/packages/core/src', 17 | '\\.(css|less)$': '/mocks/styleMock.js', 18 | }, 19 | rootDir: __dirname, 20 | testMatch: ['/packages/**/__tests__/**/*@(test|spec).[jt]s?(x)'], 21 | testPathIgnorePatterns: ['/node_modules/'], 22 | setupFiles: ['./jest.setup.js'], 23 | }; 24 | ` 25 | 26 | export const input1Content = ` 27 | module.exports = { 28 | preset: 'ts-jest', 29 | coverageDirectory: 'coverage', 30 | coverageReporters: ['html', 'lcov', 'text'], 31 | watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'], 32 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], 33 | moduleNameMapper: { 34 | '^@codeblitzjs/ide-core-(?!browserfs)(.*?)$': '/packages/$1/src', 35 | '^@codeblitzjs/ide-core$': '/packages/core/src', 36 | '\\.(css|less)$': '/mocks/styleMock.js', 37 | }, 38 | rootDir: __dirname, 39 | testMatch: ['/packages/**/__tests__/**/*@(test|spec).[jt]s?(x)'], 40 | testPathIgnorePatterns: ['/node_modules/'], 41 | setupFiles: ['./jest.setup.js'], 42 | }; 43 | ` 44 | 45 | export const input2Content = ` 46 | module.exports = { 47 | preset: 'ts-jest', 48 | coverageDirectory: 'coverage', 49 | coverageReporters: ['html', 'lcov', 'text', 'js'], 50 | collectCoverageFrom: [ 51 | 'packages/*/src/**/*.ts', 52 | '!packages/toolkit/**', 53 | '!packages/core/.kaitian/**', 54 | '!packages/core/extensions/**', 55 | ], 56 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], 57 | 58 | moduleNameMapper: { 59 | '^@codeblitzjs/ide-core-(?!browserfs)(.*?)$': '/packages/$1/src', 60 | '^@codeblitzjs/ide-core$': '/packages/core/src/ccccccc', 61 | '\\.(css|less)$': '/mocks/styleMock.js', 62 | }, 63 | testMatch: ['/packages/**/__tests__/**/*@(test|spec).[jt]s?(x)'], 64 | testPathIgnorePatterns: ['/node_modules/'], 65 | setupFiles: ['./jest.setup.js'], 66 | }; 67 | ` -------------------------------------------------------------------------------- /merge-editor/util.ts: -------------------------------------------------------------------------------- 1 | import { URI, Uri } from '@opensumi/ide-core-browser'; 2 | 3 | export const SCHEME = 'web_scm'; 4 | export const workspaceDir = 'codeblitz-resolve-conflicts'; 5 | 6 | export interface GitUriParams { 7 | path: string; 8 | ref: string; 9 | submoduleOf?: string; 10 | } 11 | 12 | export interface WebUriOptions { 13 | replaceFileExtension?: boolean; 14 | submoduleOf?: string; 15 | } 16 | 17 | export function toWebUri(uri: Uri, ref: string, options: WebUriOptions = {}): Uri { 18 | const params: GitUriParams = { 19 | path: uri.fsPath, 20 | ref 21 | }; 22 | 23 | if (options.submoduleOf) { 24 | params.submoduleOf = options.submoduleOf; 25 | } 26 | 27 | let path = uri.path; 28 | 29 | return uri.with({ 30 | scheme: 'file', 31 | path, 32 | query: JSON.stringify(params) 33 | }); 34 | } 35 | export function toMergeUris(uri: Uri): { base: Uri; ours: Uri; theirs: Uri } { 36 | const newUri = URI.file(uri.fsPath) 37 | console.log(newUri) 38 | return { 39 | // :1 :2 :3 40 | base: toWebUri(uri, ':1'), 41 | ours: toWebUri(uri, ':2').with({ path: newUri.parent.resolve('input1.mock.js').codeUri.fsPath }), 42 | theirs: toWebUri(uri, ':3').with({ path: newUri.parent.resolve('input2.mock.js').codeUri.fsPath }), 43 | }; 44 | } 45 | 46 | export const SEPARATOR = '/'; 47 | -------------------------------------------------------------------------------- /modules/registerMenu.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Provider, Autowired } from '@opensumi/di'; 2 | import { BrowserModule, CommandContribution, CommandRegistry, Disposable, Domain, AppConfig } from '@opensumi/ide-core-browser'; 3 | import { MenuContribution, IMenuRegistry, MenuId } from '@opensumi/ide-core-browser/lib/menu/next'; 4 | 5 | import { ComponentMenuGroupDivider } from '../components/ComponentMenuGroupDivider'; 6 | 7 | 8 | const TESTCOMMAND = { 9 | id: 'testcommand', 10 | } 11 | 12 | 13 | @Domain(CommandContribution, MenuContribution) 14 | export class RegisterMenuContribution implements CommandContribution, MenuContribution { 15 | @Autowired(AppConfig) 16 | appConfig: AppConfig; 17 | registerMenus(menus: IMenuRegistry): void { 18 | // 注册到 EditorTitle 19 | menus.registerMenuItem(MenuId.EditorTitle, { 20 | command: TESTCOMMAND.id, 21 | order: 100, 22 | group: 'navigation', 23 | when: 'true', 24 | }); 25 | menus.registerMenuItem(MenuId.EditorTitle, { 26 | component: ComponentMenuGroupDivider, 27 | order: 100, 28 | group: 'navigation', 29 | when: 'true', 30 | }); 31 | } 32 | 33 | registerCommands(commands: CommandRegistry): void { 34 | commands.registerCommand( 35 | { id: TESTCOMMAND.id }, 36 | { 37 | execute: () => { 38 | console.log('test command') 39 | }, 40 | }) 41 | // 解绑 重命名命令 42 | commands.unregisterCommand('file.rename') 43 | commands.registerCommand({ id: 'file.rename' }) 44 | } 45 | 46 | registerKeybindings(keybindings: KeybindingRegistry) { 47 | // 自定义注销的快捷键 48 | keybindings.unregisterKeybinding({ 49 | command: 'file.rename', 50 | keybinding: 'enter', 51 | when: 'filesExplorerFocus && !inputFocus', 52 | }); 53 | } 54 | } 55 | 56 | @Injectable() 57 | export class RegisterMenuModule extends BrowserModule { 58 | 59 | providers: Provider[] = [RegisterMenuContribution]; 60 | 61 | } -------------------------------------------------------------------------------- /modules/registerZipMenu.tsx: -------------------------------------------------------------------------------- 1 | import { FullScreenBtn, click } from '../components/zipButton'; 2 | import { requireModule } from "@codeblitzjs/ide-core/bundle"; 3 | const FileService = requireModule("@opensumi/ide-file-service"); 4 | const CommpnDI = requireModule("@opensumi/di"); 5 | const CoreBrowser = requireModule("@opensumi/ide-core-browser"); 6 | const { IFileServiceClient } = FileService; 7 | 8 | const { Injectable, Autowired, INJECTOR_TOKEN} = CommpnDI; 9 | const { Domain, BrowserModule, SlotLocation, ToolBarActionContribution, MenuId, CommandContribution} = CoreBrowser; 10 | 11 | export const getDefaultLayoutConfig = () => ({ 12 | [SlotLocation.top]: { 13 | modules: ['@opensumi/ide-menu-bar'], 14 | }, 15 | [SlotLocation.action]: { 16 | modules: [''], 17 | }, 18 | [SlotLocation.left]: { 19 | modules: ['@opensumi/ide-explorer', '@opensumi/ide-search'], 20 | }, 21 | [SlotLocation.main]: { 22 | modules: ['@opensumi/ide-editor'], 23 | }, 24 | [SlotLocation.bottom]: { 25 | modules: ['@opensumi/ide-output', '@opensumi/ide-markers'], 26 | }, 27 | [SlotLocation.statusBar]: { 28 | modules: ['@opensumi/ide-status-bar'], 29 | }, 30 | [SlotLocation.extra]: { 31 | modules: ['breadcrumb-menu'], 32 | }, 33 | [SlotLocation.action]: { 34 | modules: ['@opensumi/ide-toolbar-action'], 35 | } 36 | }); 37 | export const DOWNLOAD_ZIP = 'commands.download.zip'; 38 | 39 | @Domain(ToolBarActionContribution,CommandContribution) 40 | class RegisterMenuContribution { 41 | 42 | @Autowired(INJECTOR_TOKEN) 43 | private readonly injector; 44 | 45 | registerToolbarActions(registry) { 46 | registry.addLocation('menu-right'); 47 | registry.setDefaultLocation('menu-right'); 48 | 49 | // registry.registerToolbarAction({ 50 | // description: 'zip下载', 51 | // component: ToolBarRightBtn, 52 | // id: 'toolbar-right-btn', 53 | // weight: 1, 54 | // preferredPosition: { 55 | // location: 'menu-right', 56 | // }, 57 | // neverCollapse: true, 58 | // }); 59 | 60 | registry.registerToolbarAction({ 61 | description: '全屏', 62 | component: FullScreenBtn, 63 | id: 'full-screen', 64 | weight: 1, 65 | preferredPosition: { 66 | location: 'menu-right', 67 | }, 68 | neverCollapse: true, 69 | }); 70 | } 71 | registerMenus(menus) { 72 | // 由于目前 toolbar 尚未处理插件自定义组件展示,因此先卸载掉toolbar的右键 73 | menus.unregisterMenuId(MenuId.KTToolbarLocationContext); 74 | } 75 | // 注册下载命令 76 | registerCommands(commands): void { 77 | commands.registerCommand( 78 | { 79 | id: DOWNLOAD_ZIP, 80 | label: 'zip下载', 81 | }, 82 | { 83 | execute: async () => { 84 | const fileService = this.injector.get(IFileServiceClient); 85 | await click(fileService); 86 | }, 87 | } 88 | ); 89 | } 90 | } 91 | 92 | @Injectable() 93 | export class RegisterZipMenuModule extends BrowserModule { 94 | providers = [RegisterMenuContribution]; 95 | } -------------------------------------------------------------------------------- /modules/unregisterKeybinding.ts: -------------------------------------------------------------------------------- 1 | // 解绑 Keybinding 以及 Menu 2 | import { requireModule } from "@codeblitzjs/ide-core/bundle"; 3 | const CommpnDI = requireModule("@opensumi/di"); 4 | const CoreBrowser = requireModule("@opensumi/ide-core-browser"); 5 | 6 | const { Injectable } = CommpnDI; 7 | const { Domain, BrowserModule,MenuContribution, CommandContribution,KeybindingContribution} = CoreBrowser; 8 | interface IMenuRegistry { 9 | unregisterMenuItem(menuId: string, menuItemId: string): void; 10 | } 11 | interface KeybindingRegistry { 12 | unregisterKeybinding(keyOrBinding:string): void; 13 | } 14 | 15 | @Domain(KeybindingContribution, MenuContribution) 16 | export class KeybindContribution 17 | // implements KeybindingContribution, MenuContribution 18 | { 19 | // 命令解绑 20 | // 也可以通过 RuntimeConfig 内 unregisterKeybindings 方法注销 21 | registerKeybindings(keybindings: KeybindingRegistry) { 22 | const keybindingList = [ 23 | 'ctrlcmd+,', 24 | 'ctrlcmd+shift+p', 25 | 'ctrlcmd+p', 26 | 'F1', 27 | 'alt+n', 28 | 'alt+shift+t', 29 | 'alt+shift+w', 30 | 'ctrlcmd+\\', 31 | 'ctrlcmd+o', 32 | ]; 33 | for (let i = 1; i < 10; i++) { 34 | keybindingList.push(`ctrlcmd+${i}`); 35 | } 36 | 37 | keybindingList.forEach((binding) => { 38 | keybindings.unregisterKeybinding(binding); 39 | }); 40 | } 41 | registerMenus(menus: IMenuRegistry): void { 42 | // 移除 quickopen 43 | menus.unregisterMenuItem('editor/context', 'editor.action.quickCommand'); 44 | // menus.unregisterMenuItem(MenuId.EditorContext, QUICK_OPEN_COMMANDS.OPEN.id); 45 | } 46 | } 47 | 48 | @Injectable() 49 | export class unregisterKeybindingModule extends BrowserModule { 50 | providers = [KeybindContribution]; 51 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codeblitz-sample", 3 | "version": "1.0.0", 4 | "description": "codeblitz sample", 5 | "scripts": { 6 | "start": "npm run startup", 7 | "startup": "webpack serve --config webpack.config.js --env entry=startup", 8 | "startup:bundle": "webpack serve --config webpack.config.js --env entry=startup-bundle", 9 | "code": "webpack serve --config webpack.config.js --env entry=code", 10 | "main": "webpack serve --config webpack.config.js --env entry=main", 11 | "fs": "webpack serve --config webpack.config.js --env entry=filesystem", 12 | "editor": "webpack serve --config webpack.config.js --env entry=editor", 13 | "search": "webpack serve --config webpack.config.js --env entry=search", 14 | "zip": "webpack serve --config webpack.config.js --env entry=zip", 15 | "collaboration": "node ./collaboration/server & webpack serve --config webpack.config.js --env entry=collaboration", 16 | "conflicts": "webpack serve --config webpack.config.js --env entry=merge-editor" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "@types/react": "^18.2.0", 22 | "@types/react-dom": "^18.2.0", 23 | "css-loader": "^6.7.3", 24 | "dotenv": "^8.2.0", 25 | "file-loader": "^6.2.0", 26 | "html-webpack-plugin": "^4.5.1", 27 | "less": "^3.13.1", 28 | "less-loader": "^12.1.0", 29 | "node-polyfill-webpack-plugin": "^3.0.0", 30 | "path-browserify": "^1.0.1", 31 | "raw-loader": "^4.0.2", 32 | "style-loader": "^3.3.1", 33 | "ts-loader": "^9.5.1", 34 | "typescript": "^5.5.4", 35 | "url-loader": "^4.1.1", 36 | "webpack": "^5.89.0", 37 | "webpack-cli": "^5.1.4", 38 | "webpack-dev-server": "^4.15.1" 39 | }, 40 | "dependencies": { 41 | "@codeblitzjs/ide-core": "^2.2.1", 42 | "ahooks": "2", 43 | "antd": "^3.20.3", 44 | "file-saver": "^2.0.5", 45 | "jszip": "^3.10.1", 46 | "localforage": "^1.10.0", 47 | "lodash": "^4.17.21", 48 | "ot": "^0.0.15", 49 | "react": "^18.2.0", 50 | "react-dom": "^18.2.0", 51 | "sha1": "^1.1.1", 52 | "socket.io": "^4.7.2", 53 | "socket.io-client": "^4.7.2" 54 | }, 55 | "resolutions": { 56 | "@rjsf/core": "5.5.2", 57 | "node-gyp": "npm:@favware/skip-dependency@latest", 58 | "nsfw": "npm:@favware/skip-dependency@latest", 59 | "spdlog": "npm:@favware/skip-dependency@latest", 60 | "node-pty": "npm:@favware/skip-dependency@latest", 61 | "@parcel/watcher": "npm:@favware/skip-dependency@latest", 62 | "keytar": "npm:@favware/skip-dependency@latest" 63 | }, 64 | "repository": "git@github.com:opensumi/codeblitz-sample.git" 65 | } 66 | -------------------------------------------------------------------------------- /plugin.ts: -------------------------------------------------------------------------------- 1 | import { IPluginAPI } from '@codeblitzjs/ide-core/lib/editor'; 2 | 3 | export const PLUGIN_ID = 'editor'; 4 | 5 | let _commands: IPluginAPI['commands'] | null = null; 6 | 7 | export const api = { 8 | get commands() { 9 | return _commands; 10 | }, 11 | }; 12 | 13 | export const activate = ({ context, commands }: IPluginAPI) => { 14 | _commands = commands; 15 | context.subscriptions.push( 16 | commands.registerCommand('plugin.command.add', (x: number) => { 17 | commands.executeCommand('plugin.command.say', 'hello'); 18 | return x + x; 19 | }) 20 | ); 21 | }; 22 | 23 | export const deactivate = () => { 24 | _commands = null 25 | } 26 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode-c-sharp.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode-c-sharp", 5 | "version": "0.0.5" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode-c-sharp", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.5", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode-c-sharp", 15 | "description": "C# for Anycode", 16 | "contributes": { 17 | "anycodeLanguages": { 18 | "grammarPath": "./tree-sitter-c_sharp.wasm", 19 | "languageId": "csharp", 20 | "extensions": [ 21 | "cs" 22 | ], 23 | "queryPaths": { 24 | "comments": "./queries/comments.scm", 25 | "identifiers": "./queries/identifiers.scm", 26 | "locals": "./queries/locals.scm", 27 | "outline": "./queries/outline.scm", 28 | "references": "./queries/references.scm" 29 | }, 30 | "suppressedBy": [ 31 | "ms-dotnettools.csharp" 32 | ] 33 | } 34 | } 35 | }, 36 | "pkgNlsJSON": {}, 37 | "nlsList": [], 38 | "extendConfig": {}, 39 | "webAssets": [], 40 | "mode": "public" 41 | } 42 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode-cpp.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode-cpp", 5 | "version": "0.0.5" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode-cpp", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.5", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode-cpp", 15 | "description": "C/C++ for Anycode", 16 | "contributes": { 17 | "anycodeLanguages": [ 18 | { 19 | "grammarPath": "./tree-sitter-c.wasm", 20 | "languageId": "c", 21 | "extensions": [ 22 | "c", 23 | "i" 24 | ], 25 | "queryPaths": { 26 | "comments": "./queries/c/comments.scm", 27 | "identifiers": "./queries/c/identifiers.scm", 28 | "outline": "./queries/c/outline.scm" 29 | }, 30 | "suppressedBy": [ 31 | "ms-vscode.cpptools" 32 | ] 33 | }, 34 | { 35 | "grammarPath": "./tree-sitter-cpp.wasm", 36 | "languageId": "cpp", 37 | "extensions": [ 38 | "cpp", 39 | "cc", 40 | "cxx", 41 | "c++", 42 | "hpp", 43 | "hh", 44 | "hxx", 45 | "h++", 46 | "h", 47 | "ii", 48 | "ino", 49 | "inl", 50 | "ipp", 51 | "ixx", 52 | "hpp.in", 53 | "h.in" 54 | ], 55 | "queryPaths": { 56 | "comments": "./queries/cpp/comments.scm", 57 | "identifiers": "./queries/cpp/identifiers.scm", 58 | "outline": "./queries/cpp/outline.scm" 59 | }, 60 | "suppressedBy": [ 61 | "ms-vscode.cpptools" 62 | ] 63 | } 64 | ] 65 | } 66 | }, 67 | "pkgNlsJSON": {}, 68 | "nlsList": [], 69 | "extendConfig": {}, 70 | "webAssets": [], 71 | "mode": "public" 72 | } 73 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode-go.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode-go", 5 | "version": "0.0.5" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode-go", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.5", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode-go", 15 | "description": "Go for Anycode", 16 | "contributes": { 17 | "anycodeLanguages": { 18 | "grammarPath": "./tree-sitter-go.wasm", 19 | "languageId": "go", 20 | "extensions": [ 21 | "go" 22 | ], 23 | "queryPaths": { 24 | "comments": "./queries/comments.scm", 25 | "identifiers": "./queries/identifiers.scm", 26 | "locals": "./queries/locals.scm", 27 | "outline": "./queries/outline.scm", 28 | "references": "./queries/references.scm" 29 | }, 30 | "suppressedBy": [ 31 | "golang.Go" 32 | ] 33 | } 34 | } 35 | }, 36 | "pkgNlsJSON": {}, 37 | "nlsList": [], 38 | "extendConfig": {}, 39 | "webAssets": [], 40 | "mode": "public" 41 | } 42 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode-java.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode-java", 5 | "version": "0.0.5" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode-java", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.5", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode-java", 15 | "description": "Java for Anycode", 16 | "contributes": { 17 | "anycodeLanguages": { 18 | "grammarPath": "./tree-sitter-java.wasm", 19 | "languageId": "java", 20 | "extensions": [ 21 | "java" 22 | ], 23 | "queryPaths": { 24 | "comments": "./queries/comments.scm", 25 | "folding": "./queries/folding.scm", 26 | "identifiers": "./queries/identifiers.scm", 27 | "locals": "./queries/locals.scm", 28 | "outline": "./queries/outline.scm", 29 | "references": "./queries/references.scm" 30 | }, 31 | "suppressedBy": [ 32 | "redhat.java" 33 | ] 34 | } 35 | } 36 | }, 37 | "pkgNlsJSON": {}, 38 | "nlsList": [], 39 | "extendConfig": {}, 40 | "webAssets": [], 41 | "mode": "public" 42 | } 43 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode-php.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode-php", 5 | "version": "0.0.6" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode-php", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.6", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode-php", 15 | "description": "PHP for Anycode", 16 | "contributes": { 17 | "anycodeLanguages": { 18 | "grammarPath": "./tree-sitter-php.wasm", 19 | "languageId": "php", 20 | "extensions": [ 21 | "php", 22 | "php4", 23 | "php5", 24 | "phtml", 25 | "ctp" 26 | ], 27 | "queryPaths": { 28 | "comments": "./queries/comments.scm", 29 | "identifiers": "./queries/identifiers.scm", 30 | "locals": "./queries/locals.scm", 31 | "outline": "./queries/outline.scm", 32 | "references": "./queries/references.scm" 33 | }, 34 | "suppressedBy": [ 35 | "bmewburn.vscode-intelephense-client" 36 | ] 37 | } 38 | } 39 | }, 40 | "pkgNlsJSON": {}, 41 | "nlsList": [], 42 | "extendConfig": {}, 43 | "webAssets": [], 44 | "mode": "public" 45 | } 46 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode-python.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode-python", 5 | "version": "0.0.5" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode-python", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.5", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode-python", 15 | "description": "Python for Anycode", 16 | "contributes": { 17 | "anycodeLanguages": { 18 | "grammarPath": "./tree-sitter-python.wasm", 19 | "languageId": "python", 20 | "extensions": [ 21 | "py", 22 | "rpy", 23 | "pyw", 24 | "cpy", 25 | "gyp", 26 | "gypi", 27 | "pyi", 28 | "ipy" 29 | ], 30 | "queryPaths": { 31 | "comments": "./queries/comments.scm", 32 | "identifiers": "./queries/identifiers.scm", 33 | "locals": "./queries/locals.scm", 34 | "outline": "./queries/outline.scm", 35 | "references": "./queries/references.scm" 36 | }, 37 | "suppressedBy": [ 38 | "ms-python.python" 39 | ] 40 | } 41 | } 42 | }, 43 | "pkgNlsJSON": {}, 44 | "nlsList": [], 45 | "extendConfig": {}, 46 | "webAssets": [], 47 | "mode": "public" 48 | } 49 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode-rust.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode-rust", 5 | "version": "0.0.5" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode-rust", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.5", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode-rust", 15 | "description": "Rust for Anycode", 16 | "contributes": { 17 | "anycodeLanguages": { 18 | "grammarPath": "./tree-sitter-rust.wasm", 19 | "languageId": "rust", 20 | "extensions": [ 21 | "rs" 22 | ], 23 | "queryPaths": { 24 | "comments": "./queries/comments.scm", 25 | "folding": "./queries/folding.scm", 26 | "identifiers": "./queries/identifiers.scm", 27 | "locals": "./queries/locals.scm", 28 | "outline": "./queries/outline.scm", 29 | "references": "./queries/references.scm" 30 | }, 31 | "suppressedBy": [ 32 | "rust-lang.rust" 33 | ] 34 | } 35 | } 36 | }, 37 | "pkgNlsJSON": {}, 38 | "nlsList": [], 39 | "extendConfig": {}, 40 | "webAssets": [], 41 | "mode": "public" 42 | } 43 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode-typescript.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode-typescript", 5 | "version": "0.0.5" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode-typescript", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.5", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode-typescript", 15 | "description": "TypeScript for Anycode", 16 | "contributes": { 17 | "anycodeLanguages": { 18 | "grammarPath": "./tree-sitter-typescript.wasm", 19 | "languageId": "typescript", 20 | "extensions": [ 21 | "ts" 22 | ], 23 | "queryPaths": { 24 | "comments": "./queries/comments.scm", 25 | "identifiers": "./queries/identifiers.scm", 26 | "locals": "./queries/locals.scm", 27 | "outline": "./queries/outline.scm", 28 | "references": "./queries/references.scm" 29 | }, 30 | "suppressedBy": [ 31 | "vscode.typescript-language-features" 32 | ] 33 | } 34 | } 35 | }, 36 | "pkgNlsJSON": {}, 37 | "nlsList": [], 38 | "extendConfig": {}, 39 | "webAssets": [], 40 | "mode": "public" 41 | } 42 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.anycode.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "anycode", 5 | "version": "0.0.67" 6 | }, 7 | "packageJSON": { 8 | "name": "anycode", 9 | "publisher": "ms-vscode", 10 | "version": "0.0.67", 11 | "repository": { 12 | "url": "https://github.com/microsoft/vscode-anycode" 13 | }, 14 | "displayName": "anycode", 15 | "description": "", 16 | "activationEvents": [ 17 | "onStartupFinished", 18 | "onCommand:workbench.action.showAllSymbols" 19 | ], 20 | "contributes": { 21 | "configuration": { 22 | "title": "Anycode", 23 | "properties": { 24 | "anycode.symbolIndexSize": { 25 | "type": "number", 26 | "default": 500, 27 | "minimum": 0, 28 | "markdownDescription": "Size of the index that is used for features like symbol search and go to definition." 29 | }, 30 | "anycode.language.features": { 31 | "markdownDescription": "Control the language features that anycode offers. This can be configured for each supported language: [Learn How to Do That](https://code.visualstudio.com/docs/getstarted/settings#_languagespecific-editor-settings)", 32 | "type": "object", 33 | "scope": "language-overridable", 34 | "additionalProperties": false, 35 | "properties": { 36 | "definitions": { 37 | "type": "boolean", 38 | "description": "Go to Definition based on identifiers and local variables" 39 | }, 40 | "references": { 41 | "type": "boolean", 42 | "description": "Find References based on identifiers and local variables" 43 | }, 44 | "workspaceSymbols": { 45 | "type": "boolean", 46 | "description": "Add symbols to workspace symbol search" 47 | }, 48 | "highlights": { 49 | "type": "boolean", 50 | "description": "Highlight Occurrences of identifiers and local variables" 51 | }, 52 | "outline": { 53 | "type": "boolean", 54 | "description": "Populate Outline, Quick-outline, and Breadcrumbs" 55 | }, 56 | "completions": { 57 | "type": "boolean", 58 | "description": "Completions based on identifiers and symbol names" 59 | }, 60 | "folding": { 61 | "type": "boolean", 62 | "description": "Fold sections of codes to a single line" 63 | }, 64 | "diagnostics": { 65 | "type": "boolean", 66 | "description": "(experimental) Parse errors show as problems" 67 | } 68 | }, 69 | "default": { 70 | "completions": true, 71 | "definitions": true, 72 | "references": true, 73 | "highlights": true, 74 | "outline": true, 75 | "workspaceSymbols": true, 76 | "folding": false, 77 | "diagnostics": false 78 | } 79 | } 80 | } 81 | } 82 | }, 83 | "browser": "./dist/anycode.extension.browser.js" 84 | }, 85 | "pkgNlsJSON": {}, 86 | "nlsList": [], 87 | "extendConfig": {}, 88 | "webAssets": [], 89 | "mode": "public" 90 | } 91 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.code-runner-for-web.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "code-runner-for-web", 5 | "version": "0.1.5-patch.1" 6 | }, 7 | "packageJSON": { 8 | "name": "code-runner-for-web", 9 | "publisher": "formulahendry", 10 | "version": "0.1.5-patch.1", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/formulahendry/vscode-code-runner-for-web.git" 14 | }, 15 | "displayName": "Code Runner for Web", 16 | "description": "Run Code (Python) in browser", 17 | "icon": "images/logo.png", 18 | "activationEvents": [ 19 | "onCommand:code-runner-for-web.run" 20 | ], 21 | "contributes": { 22 | "commands": [ 23 | { 24 | "command": "code-runner-for-web.run", 25 | "title": "Run Code in Web", 26 | "icon": "$(play)" 27 | } 28 | ], 29 | "menus": { 30 | "editor/context": [ 31 | { 32 | "when": "resourceLangId == python && !inOutput", 33 | "command": "code-runner-for-web.run", 34 | "group": "navigation" 35 | } 36 | ], 37 | "editor/title/run": [ 38 | { 39 | "when": "resourceLangId == python", 40 | "command": "code-runner-for-web.run", 41 | "group": "navigation" 42 | } 43 | ] 44 | } 45 | }, 46 | "browser": "./dist/web/extension.js" 47 | }, 48 | "pkgNlsJSON": {}, 49 | "nlsList": [], 50 | "extendConfig": {}, 51 | "webAssets": [ 52 | "package.json" 53 | ], 54 | "mode": "public" 55 | } 56 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.codeswing.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "codeswing", 5 | "version": "0.0.21" 6 | }, 7 | "packageJSON": { 8 | "name": "codeswing", 9 | "publisher": "codespaces-contrib", 10 | "version": "0.0.21", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/lostintangent/codeswing" 14 | }, 15 | "displayName": "CodeSwing", 16 | "description": "Interactive coding playground for building web applications (aka swings).", 17 | "icon": "images/icon.png", 18 | "activationEvents": [ 19 | "onCommand:codeswing.newSwing", 20 | "onCommand:codeswing.newSwingDirectory", 21 | "onCommand:codeswing.newSwingFromLastTemplate", 22 | "onCommand:codeswing.openSwing", 23 | "onCommand:codeswing.openSwingInNewWindow", 24 | "workspaceContains:codeswing.json", 25 | "onStartupFinished" 26 | ], 27 | "contributes": { 28 | "commands": [ 29 | { 30 | "command": "codeswing.addLibrary", 31 | "title": "Add Library", 32 | "category": "CodeSwing", 33 | "icon": "$(library)" 34 | }, 35 | { 36 | "command": "codeswing.addSwingFile", 37 | "title": "Add File", 38 | "icon": "$(add)" 39 | }, 40 | { 41 | "command": "codeswing.changeLayout", 42 | "title": "Change Layout", 43 | "category": "CodeSwing", 44 | "icon": "$(editor-layout)" 45 | }, 46 | { 47 | "command": "codeswing.deleteSwingFile", 48 | "title": "Delete File" 49 | }, 50 | { 51 | "command": "codeswing.exportToCodePen", 52 | "title": "Export to CodePen", 53 | "category": "CodeSwing" 54 | }, 55 | { 56 | "command": "codeswing.initializeWorkspace", 57 | "title": "Initialize Workspace as Swing", 58 | "category": "CodeSwing" 59 | }, 60 | { 61 | "command": "codeswing.newSwingDirectory", 62 | "title": "New Swing in Directory...", 63 | "category": "CodeSwing" 64 | }, 65 | { 66 | "command": "codeswing.newSwing", 67 | "title": "New Swing...", 68 | "category": "CodeSwing" 69 | }, 70 | { 71 | "command": "codeswing.newSwingFromLastTemplate", 72 | "title": "New Swing from Last Template", 73 | "category": "CodeSwing" 74 | }, 75 | { 76 | "command": "codeswing.openConsole", 77 | "title": "Open Console", 78 | "category": "CodeSwing", 79 | "icon": "$(terminal)" 80 | }, 81 | { 82 | "command": "codeswing.openDeveloperTools", 83 | "title": "Open Dev Tools", 84 | "category": "CodeSwing", 85 | "icon": "$(tools)" 86 | }, 87 | { 88 | "command": "codeswing.openSwing", 89 | "title": "Open Swing...", 90 | "category": "CodeSwing" 91 | }, 92 | { 93 | "command": "codeswing.openSwingInNewWindow", 94 | "title": "Open Swing in New Window...", 95 | "category": "CodeSwing" 96 | }, 97 | { 98 | "command": "codeswing.openWorkspaceSwing", 99 | "title": "Re-Open Workspace Swing", 100 | "category": "CodeSwing" 101 | }, 102 | { 103 | "command": "codeswing.recordCodeTour", 104 | "title": "Record CodeTour" 105 | }, 106 | { 107 | "command": "codeswing.renameSwingFile", 108 | "title": "Rename File" 109 | }, 110 | { 111 | "command": "codeswing.run", 112 | "title": "Run Swing", 113 | "category": "CodeSwing", 114 | "icon": "$(play)" 115 | }, 116 | { 117 | "command": "codeswing.saveCurrentSwing", 118 | "title": "Save Current Swing As...", 119 | "category": "CodeSwing" 120 | }, 121 | { 122 | "command": "codeswing.uploadSwingFile", 123 | "title": "Upload File(s)", 124 | "icon": "$(cloud-upload)" 125 | } 126 | ], 127 | "views": { 128 | "explorer": [ 129 | { 130 | "id": "codeswing.activeSwing", 131 | "name": "CodeSwing", 132 | "when": "codeswing:inSwing && !codeswing:inSwingWorkspace" 133 | } 134 | ] 135 | }, 136 | "menus": { 137 | "commandPalette": [ 138 | { 139 | "command": "codeswing.addLibrary", 140 | "when": "codeswing:inSwing" 141 | }, 142 | { 143 | "command": "codeswing.changeLayout", 144 | "when": "codeswing:inSwing" 145 | }, 146 | { 147 | "command": "codeswing.exportToCodePen", 148 | "when": "codeswing:inSwing" 149 | }, 150 | { 151 | "command": "codeswing.initializeWorkspace", 152 | "when": "!codeswing:inSwingWorkspace && !codeswing:inSwing" 153 | }, 154 | { 155 | "command": "codeswing.openConsole", 156 | "when": "codeswing:inSwing" 157 | }, 158 | { 159 | "command": "codeswing.newSwingFromLastTemplate", 160 | "when": "codeswing:hasTemplateMRU" 161 | }, 162 | { 163 | "command": "codeswing.openDeveloperTools", 164 | "when": "codeswing:inSwing && !isWeb" 165 | }, 166 | { 167 | "command": "codeswing.openWorkspaceSwing", 168 | "when": "codeswing:inSwingWorkspace && !codeswing:inSwing" 169 | }, 170 | { 171 | "command": "codeswing.run", 172 | "when": "codeswing:inSwing" 173 | }, 174 | { 175 | "command": "codeswing.saveCurrentSwing", 176 | "when": "codeswing:inSwing" 177 | }, 178 | { 179 | "command": "codeswing.addSwingFile", 180 | "when": "false" 181 | }, 182 | { 183 | "command": "codeswing.deleteSwingFile", 184 | "when": "false" 185 | }, 186 | { 187 | "command": "codeswing.recordCodeTour", 188 | "when": "false" 189 | }, 190 | { 191 | "command": "codeswing.renameSwingFile", 192 | "when": "false" 193 | }, 194 | { 195 | "command": "codeswing.uploadSwingFile", 196 | "when": "false" 197 | } 198 | ], 199 | "editor/title": [ 200 | { 201 | "command": "codeswing.run", 202 | "when": "codeswing:inSwing", 203 | "group": "navigation@1" 204 | }, 205 | { 206 | "command": "codeswing.openConsole", 207 | "when": "codeswing:inSwing", 208 | "group": "navigation@2" 209 | }, 210 | { 211 | "command": "codeswing.changeLayout", 212 | "when": "codeswing:inSwing", 213 | "group": "navigation@3" 214 | }, 215 | { 216 | "command": "codeswing.addLibrary", 217 | "when": "codeswing:inSwing", 218 | "group": "navigation@4" 219 | }, 220 | { 221 | "command": "codeswing.openDeveloperTools", 222 | "when": "codeswing:inSwing && !isWeb", 223 | "group": "navigation@5" 224 | }, 225 | { 226 | "command": "codeswing.recordCodeTour", 227 | "when": "codeswing:inSwing && codeswing:codeTourEnabled", 228 | "group": "codetour@1" 229 | } 230 | ], 231 | "view/title": [ 232 | { 233 | "command": "codeswing.uploadSwingFile", 234 | "when": "view == codeswing.activeSwing", 235 | "group": "navigation@1" 236 | }, 237 | { 238 | "command": "codeswing.addSwingFile", 239 | "when": "view == codeswing.activeSwing", 240 | "group": "navigation@2" 241 | } 242 | ], 243 | "view/item/context": [ 244 | { 245 | "command": "codeswing.addSwingFile", 246 | "when": "viewItem == swing.directory", 247 | "group": "mutation@1" 248 | }, 249 | { 250 | "command": "codeswing.uploadSwingFile", 251 | "when": "viewItem == swing.directory", 252 | "group": "mutation@2" 253 | }, 254 | { 255 | "command": "codeswing.renameSwingFile", 256 | "when": "viewItem == swing.file", 257 | "group": "mutation@1" 258 | }, 259 | { 260 | "command": "codeswing.deleteSwingFile", 261 | "when": "viewItem == swing.file", 262 | "group": "mutation@2" 263 | } 264 | ] 265 | }, 266 | "jsonValidation": [ 267 | { 268 | "fileMatch": "codeswing.json", 269 | "url": "https://gist.githubusercontent.com/lostintangent/21727eab0d79c7b9fd0dde92df7b1f50/raw/schema.json" 270 | }, 271 | { 272 | "fileMatch": "gallery.json", 273 | "url": "https://gist.githubusercontent.com/lostintangent/091c0eec1f6443b526566d1cd3a85294/raw/schema.json" 274 | } 275 | ], 276 | "configuration": { 277 | "type": "object", 278 | "title": "CodeSwing", 279 | "properties": { 280 | "codeswing.autoRun": { 281 | "default": "onEdit", 282 | "enum": [ 283 | "onEdit", 284 | "onSave", 285 | "never" 286 | ], 287 | "description": "Specifies when to automatically run the code for a swing." 288 | }, 289 | "codeswing.autoSave": { 290 | "default": false, 291 | "type": "boolean", 292 | "description": "Specifies whether to automatically save your swing files (every 30s)." 293 | }, 294 | "codeswing.clearConsoleOnRun": { 295 | "default": true, 296 | "type": "boolean", 297 | "description": "Specifies whether to automatically clear the console when running a swing." 298 | }, 299 | "codeswing.launchBehavior": { 300 | "default": "openSwing", 301 | "enum": [ 302 | "newSwing", 303 | "none", 304 | "openSwing" 305 | ], 306 | "description": "Specifies how CodeSwing should behave when you open a swing workspace." 307 | }, 308 | "codeswing.layout": { 309 | "default": "splitLeft", 310 | "enum": [ 311 | "grid", 312 | "preview", 313 | "splitBottom", 314 | "splitLeft", 315 | "splitLeftTabbed", 316 | "splitRight", 317 | "splitRightTabbed", 318 | "splitTop" 319 | ], 320 | "description": "Specifies how to layout the editor windows when opening a swing." 321 | }, 322 | "codeswing.readmeBehavior": { 323 | "default": "none", 324 | "enum": [ 325 | "none", 326 | "previewFooter", 327 | "previewHeader" 328 | ], 329 | "description": "Specifies how the swing's readme content should be displayed." 330 | }, 331 | "codeswing.rootDirectory": { 332 | "default": null, 333 | "type": "string", 334 | "description": "Specifies the directory to create swings in within the open workspace." 335 | }, 336 | "codeswing.showConsole": { 337 | "default": false, 338 | "type": "boolean", 339 | "description": "Specifies whether to automatically show the console when opening a swing." 340 | }, 341 | "codeswing.templateGalleries": { 342 | "default": [ 343 | "web:basic", 344 | "web:components", 345 | "web:languages" 346 | ], 347 | "type": "array", 348 | "items": { 349 | "anyOf": [ 350 | { 351 | "type": "string", 352 | "enum": [ 353 | "web:basic", 354 | "web:components", 355 | "web:languages" 356 | ] 357 | }, 358 | { 359 | "type": "string", 360 | "format": "uri" 361 | } 362 | ] 363 | }, 364 | "description": "Specifies one or more URLs that point of template galleries for creating swings." 365 | }, 366 | "codeswing.themePreview": { 367 | "default": false, 368 | "type": "boolean", 369 | "description": "Specifies whether to apply Visual Studio Code theme to the preview window." 370 | } 371 | } 372 | }, 373 | "languages": [ 374 | { 375 | "id": "typescriptreact", 376 | "filenames": [ 377 | "script.babel" 378 | ] 379 | }, 380 | { 381 | "id": "yaml", 382 | "filenames": [ 383 | ".block" 384 | ] 385 | } 386 | ], 387 | "keybindings": [ 388 | { 389 | "command": "codeswing.run", 390 | "when": "codeswing:inSwing", 391 | "key": "cmd+shift+b", 392 | "win": "ctrl+shift+b" 393 | } 394 | ], 395 | "codeswing.templateGalleries": [ 396 | { 397 | "id": "web:basic", 398 | "url": "https://cdn.jsdelivr.net/gh/codespaces-contrib/codeswing@HEAD/templates/basic.json" 399 | }, 400 | { 401 | "id": "web:languages", 402 | "url": "https://cdn.jsdelivr.net/gh/codespaces-contrib/codeswing@HEAD/templates/languages.json" 403 | }, 404 | { 405 | "id": "web:components", 406 | "url": "https://cdn.jsdelivr.net/gh/codespaces-contrib/codeswing@main/templates/components.json" 407 | } 408 | ] 409 | }, 410 | "browser": "./dist/extension-web.js" 411 | }, 412 | "pkgNlsJSON": {}, 413 | "nlsList": [], 414 | "extendConfig": {}, 415 | "webAssets": [], 416 | "mode": "public" 417 | } 418 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.html-language-features-worker.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "html-language-features-worker", 5 | "version": "1.53.0-patch.1" 6 | }, 7 | "packageJSON": { 8 | "name": "html-language-features-worker", 9 | "publisher": "alex", 10 | "version": "1.53.0-patch.1", 11 | "displayName": "%displayName%", 12 | "description": "%description%", 13 | "icon": "icons/html.png", 14 | "activationEvents": [ 15 | "onLanguage:html", 16 | "onLanguage:handlebars" 17 | ], 18 | "kaitianContributes": { 19 | "workerMain": "client/dist/browser/htmlClientMain.js" 20 | }, 21 | "contributes": { 22 | "configuration": { 23 | "id": "html", 24 | "order": 20, 25 | "type": "object", 26 | "title": "HTML", 27 | "properties": { 28 | "html.customData": { 29 | "type": "array", 30 | "markdownDescription": "%html.customData.desc%", 31 | "default": [], 32 | "items": { 33 | "type": "string" 34 | }, 35 | "scope": "resource" 36 | }, 37 | "html.format.enable": { 38 | "type": "boolean", 39 | "scope": "window", 40 | "default": true, 41 | "description": "%html.format.enable.desc%" 42 | }, 43 | "html.format.wrapLineLength": { 44 | "type": "integer", 45 | "scope": "resource", 46 | "default": 120, 47 | "description": "%html.format.wrapLineLength.desc%" 48 | }, 49 | "html.format.unformatted": { 50 | "type": [ 51 | "string", 52 | "null" 53 | ], 54 | "scope": "resource", 55 | "default": "wbr", 56 | "markdownDescription": "%html.format.unformatted.desc%" 57 | }, 58 | "html.format.contentUnformatted": { 59 | "type": [ 60 | "string", 61 | "null" 62 | ], 63 | "scope": "resource", 64 | "default": "pre,code,textarea", 65 | "markdownDescription": "%html.format.contentUnformatted.desc%" 66 | }, 67 | "html.format.indentInnerHtml": { 68 | "type": "boolean", 69 | "scope": "resource", 70 | "default": false, 71 | "markdownDescription": "%html.format.indentInnerHtml.desc%" 72 | }, 73 | "html.format.preserveNewLines": { 74 | "type": "boolean", 75 | "scope": "resource", 76 | "default": true, 77 | "description": "%html.format.preserveNewLines.desc%" 78 | }, 79 | "html.format.maxPreserveNewLines": { 80 | "type": [ 81 | "number", 82 | "null" 83 | ], 84 | "scope": "resource", 85 | "default": null, 86 | "markdownDescription": "%html.format.maxPreserveNewLines.desc%" 87 | }, 88 | "html.format.indentHandlebars": { 89 | "type": "boolean", 90 | "scope": "resource", 91 | "default": false, 92 | "markdownDescription": "%html.format.indentHandlebars.desc%" 93 | }, 94 | "html.format.endWithNewline": { 95 | "type": "boolean", 96 | "scope": "resource", 97 | "default": false, 98 | "description": "%html.format.endWithNewline.desc%" 99 | }, 100 | "html.format.extraLiners": { 101 | "type": [ 102 | "string", 103 | "null" 104 | ], 105 | "scope": "resource", 106 | "default": "head, body, /html", 107 | "markdownDescription": "%html.format.extraLiners.desc%" 108 | }, 109 | "html.format.wrapAttributes": { 110 | "type": "string", 111 | "scope": "resource", 112 | "default": "auto", 113 | "enum": [ 114 | "auto", 115 | "force", 116 | "force-aligned", 117 | "force-expand-multiline", 118 | "aligned-multiple", 119 | "preserve", 120 | "preserve-aligned" 121 | ], 122 | "enumDescriptions": [ 123 | "%html.format.wrapAttributes.auto%", 124 | "%html.format.wrapAttributes.force%", 125 | "%html.format.wrapAttributes.forcealign%", 126 | "%html.format.wrapAttributes.forcemultiline%", 127 | "%html.format.wrapAttributes.alignedmultiple%", 128 | "%html.format.wrapAttributes.preserve%", 129 | "%html.format.wrapAttributes.preservealigned%" 130 | ], 131 | "description": "%html.format.wrapAttributes.desc%" 132 | }, 133 | "html.format.wrapAttributesIndentSize": { 134 | "type": [ 135 | "number", 136 | "null" 137 | ], 138 | "scope": "resource", 139 | "default": null, 140 | "description": "%html.format.wrapAttributesIndentSize.desc%" 141 | }, 142 | "html.format.templating": { 143 | "type": [ 144 | "boolean" 145 | ], 146 | "scope": "resource", 147 | "default": false, 148 | "description": "%html.format.templating.desc%" 149 | }, 150 | "html.format.unformattedContentDelimiter": { 151 | "type": [ 152 | "string" 153 | ], 154 | "scope": "resource", 155 | "default": "", 156 | "markdownDescription": "%html.format.unformattedContentDelimiter.desc%" 157 | }, 158 | "html.suggest.html5": { 159 | "type": "boolean", 160 | "scope": "resource", 161 | "default": true, 162 | "description": "%html.suggest.html5.desc%" 163 | }, 164 | "html.validate.scripts": { 165 | "type": "boolean", 166 | "scope": "resource", 167 | "default": true, 168 | "description": "%html.validate.scripts%" 169 | }, 170 | "html.validate.styles": { 171 | "type": "boolean", 172 | "scope": "resource", 173 | "default": true, 174 | "description": "%html.validate.styles%" 175 | }, 176 | "html.autoClosingTags": { 177 | "type": "boolean", 178 | "scope": "resource", 179 | "default": true, 180 | "description": "%html.autoClosingTags%" 181 | }, 182 | "html.hover.documentation": { 183 | "type": "boolean", 184 | "scope": "resource", 185 | "default": true, 186 | "description": "%html.hover.documentation%" 187 | }, 188 | "html.hover.references": { 189 | "type": "boolean", 190 | "scope": "resource", 191 | "default": true, 192 | "description": "%html.hover.references%" 193 | }, 194 | "html.mirrorCursorOnMatchingTag": { 195 | "type": "boolean", 196 | "scope": "resource", 197 | "default": false, 198 | "description": "%html.mirrorCursorOnMatchingTag%", 199 | "deprecationMessage": "%html.mirrorCursorOnMatchingTagDeprecationMessage%" 200 | }, 201 | "html.trace.server": { 202 | "type": "string", 203 | "scope": "window", 204 | "enum": [ 205 | "off", 206 | "messages", 207 | "verbose" 208 | ], 209 | "default": "off", 210 | "description": "%html.trace.server.desc%" 211 | } 212 | } 213 | }, 214 | "configurationDefaults": { 215 | "[html]": { 216 | "editor.suggest.insertMode": "replace" 217 | }, 218 | "[handlebars]": { 219 | "editor.suggest.insertMode": "replace" 220 | } 221 | }, 222 | "jsonValidation": [ 223 | { 224 | "fileMatch": "*.html-data.json", 225 | "url": "https://raw.githubusercontent.com/microsoft/vscode-html-languageservice/master/docs/customData.schema.json" 226 | }, 227 | { 228 | "fileMatch": "package.json", 229 | "url": "./schemas/package.schema.json" 230 | } 231 | ], 232 | "workerMain": "client/dist/browser/htmlClientMain.js" 233 | }, 234 | "browser": "./client/dist/browser/htmlClientMain" 235 | }, 236 | "defaultPkgNlsJSON": { 237 | "displayName": "HTML Language Features", 238 | "description": "Provides rich language support for HTML and Handlebar files", 239 | "html.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", 240 | "html.format.enable.desc": "Enable/disable default HTML formatter.", 241 | "html.format.wrapLineLength.desc": "Maximum amount of characters per line (0 = disable).", 242 | "html.format.unformatted.desc": "List of tags, comma separated, that shouldn't be reformatted. `null` defaults to all tags listed at https://www.w3.org/TR/html5/dom.html#phrasing-content.", 243 | "html.format.contentUnformatted.desc": "List of tags, comma separated, where the content shouldn't be reformatted. `null` defaults to the `pre` tag.", 244 | "html.format.indentInnerHtml.desc": "Indent `` and `` sections.", 245 | "html.format.preserveNewLines.desc": "Controls whether existing line breaks before elements should be preserved. Only works before elements, not inside tags or for text.", 246 | "html.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk. Use `null` for unlimited.", 247 | "html.format.indentHandlebars.desc": "Format and indent `{{#foo}}` and `{{/foo}}`.", 248 | "html.format.endWithNewline.desc": "End with a newline.", 249 | "html.format.extraLiners.desc": "List of tags, comma separated, that should have an extra newline before them. `null` defaults to `\"head, body, /html\"`.", 250 | "html.format.wrapAttributes.desc": "Wrap attributes.", 251 | "html.format.wrapAttributes.auto": "Wrap attributes only when line length is exceeded.", 252 | "html.format.wrapAttributes.force": "Wrap each attribute except first.", 253 | "html.format.wrapAttributes.forcealign": "Wrap each attribute except first and keep aligned.", 254 | "html.format.wrapAttributes.forcemultiline": "Wrap each attribute.", 255 | "html.format.wrapAttributes.alignedmultiple": "Wrap when line length is exceeded, align attributes vertically.", 256 | "html.format.wrapAttributes.preserve": "Preserve wrapping of attributes", 257 | "html.format.wrapAttributes.preservealigned": "Preserve wrapping of attributes but align.", 258 | "html.format.templating.desc": "Honor django, erb, handlebars and php templating language tags.", 259 | "html.format.unformattedContentDelimiter.desc": "Keep text content together between this string.", 260 | "html.format.wrapAttributesIndentSize.desc": "Alignment size when using 'force aligned' and 'aligned multiple' in `#html.format.wrapAttributes#` or `null` to use the default indent size.", 261 | "html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.", 262 | "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", 263 | "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", 264 | "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", 265 | "html.autoClosingTags": "Enable/disable autoclosing of HTML tags.", 266 | "html.mirrorCursorOnMatchingTag": "Enable/disable mirroring cursor on matching HTML tag.", 267 | "html.mirrorCursorOnMatchingTagDeprecationMessage": "Deprecated in favor of `editor.linkedEditing`", 268 | "html.hover.documentation": "Show tag and attribute documentation in hover.", 269 | "html.hover.references": "Show references to MDN in hover." 270 | }, 271 | "pkgNlsJSON": {}, 272 | "nlsList": [], 273 | "extendConfig": {}, 274 | "webAssets": [ 275 | "package.json", 276 | "server/dist/browser/htmlServerMain.js", 277 | "README.md", 278 | "icons/html.png", 279 | "https://raw.githubusercontent.com/microsoft/vscode-html-languageservice/master/docs/customData.schema.json", 280 | "schemas/package.schema.json", 281 | "client/dist/browser/htmlClientMain.js" 282 | ], 283 | "mode": "public" 284 | } 285 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.image-preview.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "image-preview", 5 | "version": "1.53.0-patch.1" 6 | }, 7 | "packageJSON": { 8 | "name": "image-preview", 9 | "publisher": "alex-ext-public", 10 | "version": "1.53.0-patch.1", 11 | "displayName": "%displayName%", 12 | "description": "%description%", 13 | "icon": "icon.png", 14 | "activationEvents": [ 15 | "onCustomEditor:imagePreview.previewEditor", 16 | "onCommand:imagePreview.zoomIn", 17 | "onCommand:imagePreview.zoomOut" 18 | ], 19 | "kaitianContributes": { 20 | "workerMain": "./dist/browser/extension.js" 21 | }, 22 | "contributes": { 23 | "customEditors": [ 24 | { 25 | "viewType": "imagePreview.previewEditor", 26 | "displayName": "%customEditors.displayName%", 27 | "priority": "builtin", 28 | "selector": [ 29 | { 30 | "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp}" 31 | } 32 | ] 33 | } 34 | ], 35 | "commands": [ 36 | { 37 | "command": "imagePreview.zoomIn", 38 | "title": "%command.zoomIn%", 39 | "category": "Image Preview" 40 | }, 41 | { 42 | "command": "imagePreview.zoomOut", 43 | "title": "%command.zoomOut%", 44 | "category": "Image Preview" 45 | } 46 | ], 47 | "menus": { 48 | "commandPalette": [ 49 | { 50 | "command": "imagePreview.zoomIn", 51 | "when": "imagePreviewFocus", 52 | "group": "1_imagePreview" 53 | }, 54 | { 55 | "command": "imagePreview.zoomOut", 56 | "when": "imagePreviewFocus", 57 | "group": "1_imagePreview" 58 | } 59 | ] 60 | }, 61 | "workerMain": "./dist/browser/extension.js" 62 | }, 63 | "browser": "./dist/browser/extension.js" 64 | }, 65 | "defaultPkgNlsJSON": { 66 | "displayName": "Image Preview", 67 | "description": "Provides VS Code's built-in image preview", 68 | "customEditors.displayName": "Image Preview", 69 | "command.zoomIn": "Zoom in", 70 | "command.zoomOut": "Zoom out" 71 | }, 72 | "pkgNlsJSON": {}, 73 | "nlsList": [], 74 | "extendConfig": {}, 75 | "webAssets": [ 76 | "package.json", 77 | "media/main.js", 78 | "media/main.css", 79 | "media/loading.svg", 80 | "media/loading-dark.svg", 81 | "media/loading-hc.svg", 82 | "README.md", 83 | "icon.png", 84 | "icon.svg", 85 | "dist/browser/extension.js" 86 | ], 87 | "mode": "public" 88 | } 89 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.json-language-features-worker.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "json-language-features-worker", 5 | "version": "1.53.0-patch.1" 6 | }, 7 | "packageJSON": { 8 | "name": "json-language-features-worker", 9 | "publisher": "alex", 10 | "version": "1.53.0-patch.1", 11 | "displayName": "%displayName%", 12 | "description": "%description%", 13 | "icon": "icons/json.png", 14 | "activationEvents": [ 15 | "onLanguage:json", 16 | "onLanguage:jsonc" 17 | ], 18 | "kaitianContributes": { 19 | "workerMain": "client/dist/browser/jsonClientMain.js" 20 | }, 21 | "contributes": { 22 | "configuration": { 23 | "id": "json", 24 | "order": 20, 25 | "type": "object", 26 | "title": "JSON", 27 | "properties": { 28 | "json.schemas": { 29 | "type": "array", 30 | "scope": "resource", 31 | "description": "%json.schemas.desc%", 32 | "items": { 33 | "type": "object", 34 | "default": { 35 | "fileMatch": [ 36 | "/myfile" 37 | ], 38 | "url": "schemaURL" 39 | }, 40 | "properties": { 41 | "url": { 42 | "type": "string", 43 | "default": "/user.schema.json", 44 | "description": "%json.schemas.url.desc%" 45 | }, 46 | "fileMatch": { 47 | "type": "array", 48 | "items": { 49 | "type": "string", 50 | "default": "MyFile.json", 51 | "description": "%json.schemas.fileMatch.item.desc%" 52 | }, 53 | "minItems": 1, 54 | "description": "%json.schemas.fileMatch.desc%" 55 | }, 56 | "schema": { 57 | "$ref": "http://json-schema.org/draft-07/schema#", 58 | "description": "%json.schemas.schema.desc%" 59 | } 60 | } 61 | } 62 | }, 63 | "json.format.enable": { 64 | "type": "boolean", 65 | "scope": "window", 66 | "default": true, 67 | "description": "%json.format.enable.desc%" 68 | }, 69 | "json.trace.server": { 70 | "type": "string", 71 | "scope": "window", 72 | "enum": [ 73 | "off", 74 | "messages", 75 | "verbose" 76 | ], 77 | "default": "off", 78 | "description": "%json.tracing.desc%" 79 | }, 80 | "json.colorDecorators.enable": { 81 | "type": "boolean", 82 | "scope": "window", 83 | "default": true, 84 | "description": "%json.colorDecorators.enable.desc%", 85 | "deprecationMessage": "%json.colorDecorators.enable.deprecationMessage%" 86 | }, 87 | "json.maxItemsComputed": { 88 | "type": "number", 89 | "default": 5000, 90 | "description": "%json.maxItemsComputed.desc%" 91 | }, 92 | "json.schemaDownload.enable": { 93 | "type": "boolean", 94 | "default": true, 95 | "description": "%json.enableSchemaDownload.desc%", 96 | "tags": [ 97 | "usesOnlineServices" 98 | ] 99 | } 100 | } 101 | }, 102 | "configurationDefaults": { 103 | "[json]": { 104 | "editor.quickSuggestions": { 105 | "strings": true 106 | }, 107 | "editor.suggest.insertMode": "replace" 108 | }, 109 | "[jsonc]": { 110 | "editor.quickSuggestions": { 111 | "strings": true 112 | }, 113 | "editor.suggest.insertMode": "replace" 114 | } 115 | }, 116 | "jsonValidation": [ 117 | { 118 | "fileMatch": "*.schema.json", 119 | "url": "http://json-schema.org/draft-07/schema#" 120 | } 121 | ], 122 | "workerMain": "client/dist/browser/jsonClientMain.js" 123 | }, 124 | "browser": "./client/dist/browser/jsonClientMain" 125 | }, 126 | "defaultPkgNlsJSON": { 127 | "displayName": "JSON Language Features", 128 | "description": "Provides rich language support for JSON files.", 129 | "json.schemas.desc": "Associate schemas to JSON files in the current project", 130 | "json.schemas.url.desc": "A URL to a schema or a relative path to a schema in the current directory", 131 | "json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas. `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there is at least one matching pattern and the last matching pattern is not an exclusion pattern.", 132 | "json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.", 133 | "json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.", 134 | "json.format.enable.desc": "Enable/disable default JSON formatter", 135 | "json.tracing.desc": "Traces the communication between VS Code and the JSON language server.", 136 | "json.colorDecorators.enable.desc": "Enables or disables color decorators", 137 | "json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", 138 | "json.schemaResolutionErrorMessage": "Unable to resolve schema.", 139 | "json.clickToRetry": "Click to retry.", 140 | "json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).", 141 | "json.maxItemsExceededInformation.desc": "Show notification when exceeding the maximum number of outline symbols and folding regions.", 142 | "json.enableSchemaDownload.desc": "When enabled, JSON schemas can be fetched from http and https locations." 143 | }, 144 | "pkgNlsJSON": {}, 145 | "nlsList": [], 146 | "extendConfig": {}, 147 | "webAssets": [ 148 | "package.json", 149 | "server/dist/browser/jsonServerMain.js", 150 | "README.md", 151 | "icons/json.png", 152 | "http://json-schema.org/draft-07/schema#", 153 | "client/dist/browser/jsonClientMain.js" 154 | ], 155 | "mode": "public" 156 | } 157 | -------------------------------------------------------------------------------- /public-extensions/alex-ext-public.web-scm.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extension": { 3 | "publisher": "alex-ext-public", 4 | "name": "web-scm", 5 | "version": "0.0.11" 6 | }, 7 | "packageJSON": { 8 | "name": "web-scm", 9 | "publisher": "alex-ext-public", 10 | "version": "0.0.10", 11 | "displayName": "web-scm", 12 | "description": "SCM for Web", 13 | "activationEvents": [ 14 | "*" 15 | ], 16 | "kaitianContributes": { 17 | "workerMain": "./out/worker/index.js" 18 | }, 19 | "contributes": { 20 | "commands": [ 21 | { 22 | "command": "git.commit", 23 | "title": "git commit", 24 | "icon": "$(check)", 25 | "category": "Git" 26 | }, 27 | { 28 | "command": "git.refresh", 29 | "title": "git refresh", 30 | "icon": "$(refresh)", 31 | "category": "Git" 32 | }, 33 | { 34 | "command": "git.branch", 35 | "title": "create branch", 36 | "icon": "$(branch)", 37 | "category": "Git" 38 | }, 39 | { 40 | "command": "git.branchFrom", 41 | "title": "create branch from", 42 | "icon": "$(branch)", 43 | "category": "Git" 44 | }, 45 | { 46 | "command": "git.openFile", 47 | "title": "git open file", 48 | "icon": "$(go-to-file)", 49 | "category": "Git" 50 | }, 51 | { 52 | "command": "git.stage", 53 | "title": "git stage", 54 | "icon": "$(add)", 55 | "category": "Git" 56 | }, 57 | { 58 | "command": "git.clean", 59 | "title": "git clean", 60 | "icon": "$(discard)", 61 | "category": "Git" 62 | }, 63 | { 64 | "command": "git.cleanAll", 65 | "title": "git cleanAll", 66 | "icon": "$(discard)", 67 | "category": "Git" 68 | } 69 | ], 70 | "menus": { 71 | "scm/title": [ 72 | { 73 | "command": "git.commit", 74 | "group": "navigation", 75 | "when": "scmProvider == webscm" 76 | }, 77 | { 78 | "command": "git.refresh", 79 | "group": "navigation", 80 | "when": "scmProvider == webscm" 81 | }, 82 | { 83 | "command": "git.branch", 84 | "when": "scmProvider == webscm && createBranchAble" 85 | }, 86 | { 87 | "command": "git.branchFrom", 88 | "when": "scmProvider == webscm && createBranchAble" 89 | } 90 | ], 91 | "scm/resourceState/context": [ 92 | { 93 | "command": "git.openFile", 94 | "when": "scmProvider == webscm", 95 | "group": "inline" 96 | }, 97 | { 98 | "command": "git.clean", 99 | "when": "scmProvider == webscm", 100 | "group": "inline" 101 | } 102 | ], 103 | "scm/resourceGroup/context": [ 104 | { 105 | "command": "git.cleanAll", 106 | "when": "scmProvider == webscm", 107 | "group": "inline" 108 | } 109 | ], 110 | "scm/change/title": [ 111 | { 112 | "command": "git.cleanAll", 113 | "when": "originalResourceScheme == git" 114 | } 115 | ] 116 | }, 117 | "colors": [ 118 | { 119 | "id": "gitDecoration.addedResourceForeground", 120 | "description": "%colors.added%", 121 | "defaults": { 122 | "light": "#587c0c", 123 | "dark": "#81b88b", 124 | "highContrast": "#1b5225", 125 | "highContrastLight": "#374e06" 126 | } 127 | }, 128 | { 129 | "id": "gitDecoration.modifiedResourceForeground", 130 | "description": "%colors.modified%", 131 | "defaults": { 132 | "light": "#895503", 133 | "dark": "#E2C08D", 134 | "highContrast": "#E2C08D", 135 | "highContrastLight": "#895503" 136 | } 137 | }, 138 | { 139 | "id": "gitDecoration.deletedResourceForeground", 140 | "description": "%colors.deleted%", 141 | "defaults": { 142 | "light": "#ad0707", 143 | "dark": "#c74e39", 144 | "highContrast": "#c74e39", 145 | "highContrastLight": "#ad0707" 146 | } 147 | }, 148 | { 149 | "id": "gitDecoration.renamedResourceForeground", 150 | "description": "%colors.renamed%", 151 | "defaults": { 152 | "light": "#007100", 153 | "dark": "#73C991", 154 | "highContrast": "#73C991", 155 | "highContrastLight": "#007100" 156 | } 157 | }, 158 | { 159 | "id": "gitDecoration.untrackedResourceForeground", 160 | "description": "%colors.untracked%", 161 | "defaults": { 162 | "light": "#007100", 163 | "dark": "#73C991", 164 | "highContrast": "#73C991", 165 | "highContrastLight": "#007100" 166 | } 167 | }, 168 | { 169 | "id": "gitDecoration.ignoredResourceForeground", 170 | "description": "%colors.ignored%", 171 | "defaults": { 172 | "light": "#8E8E90", 173 | "dark": "#8C8C8C", 174 | "highContrast": "#A7A8A9", 175 | "highContrastLight": "#8e8e90" 176 | } 177 | }, 178 | { 179 | "id": "gitDecoration.stageModifiedResourceForeground", 180 | "description": "%colors.stageModified%", 181 | "defaults": { 182 | "light": "#895503", 183 | "dark": "#E2C08D", 184 | "highContrast": "#E2C08D", 185 | "highContrastLight": "#895503" 186 | } 187 | }, 188 | { 189 | "id": "gitDecoration.stageDeletedResourceForeground", 190 | "description": "%colors.stageDeleted%", 191 | "defaults": { 192 | "light": "#ad0707", 193 | "dark": "#c74e39", 194 | "highContrast": "#c74e39", 195 | "highContrastLight": "#ad0707" 196 | } 197 | }, 198 | { 199 | "id": "gitDecoration.conflictingResourceForeground", 200 | "description": "%colors.conflict%", 201 | "defaults": { 202 | "light": "#ad0707", 203 | "dark": "#e4676b", 204 | "highContrast": "#c74e39", 205 | "highContrastLight": "#ad0707" 206 | } 207 | }, 208 | { 209 | "id": "gitDecoration.submoduleResourceForeground", 210 | "description": "%colors.submodule%", 211 | "defaults": { 212 | "light": "#1258a7", 213 | "dark": "#8db9e2", 214 | "highContrast": "#8db9e2", 215 | "highContrastLight": "#1258a7" 216 | } 217 | } 218 | ], 219 | "keybindings": [ 220 | { 221 | "command": "git.commit", 222 | "key": "ctrl+enter", 223 | "mac": "cmd+enter", 224 | "when": "scmRepository" 225 | } 226 | ], 227 | "workerMain": "./out/worker/index.js" 228 | } 229 | }, 230 | "defaultPkgNlsJSON": { 231 | "common.cancel": "Cancel", 232 | "common.certain": "Certain", 233 | "alex.git.refresh": "git refresh", 234 | "alex.git.inputBox.placeholder": "Message ({0} commit and push directly)", 235 | "changes": "Changes", 236 | "staged.changes": "Staged Changes", 237 | "merge.changes": "Merge Changes", 238 | "untracked.changes": "Untracked Changes", 239 | "git.title.workingTree": "{0} (WorkingTree)", 240 | "git.title.untracked": "{0} (Untracked)", 241 | "git.title.deleted": "{0} (Deleted)", 242 | "git.title.added": "{0} (Added)", 243 | "git error details": "Git Error", 244 | "deleted": "Deleted", 245 | "added": "Added", 246 | "modified": "Modified", 247 | "diff": "Diff", 248 | "discard": "Discard Changes", 249 | "confirm delete": "Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST if you proceed.", 250 | "restore file": "Restore file", 251 | "confirm restore": "Are you sure you want to restore {0}?", 252 | "confirm discard": "Are you sure you want to discard changes in {0}?", 253 | "delete file": "Delete file", 254 | "delete files": "Delete Files", 255 | "restore files": "Restore files", 256 | "confirm restore multiple": "Are you sure you want to restore {0} files?", 257 | "confirm discard multiple": "Are you sure you want to discard changes in {0} files?", 258 | "warn untracked": "This will DELETE {0} untracked files!\nThis is IRREVERSIBLE!\nThese files will be FOREVER LOST.", 259 | "there are untracked files single": "The following untracked file will be DELETED FROM DISK if discarded: {0}.", 260 | "there are untracked files": "There are {0} untracked files which will be DELETED FROM DISK if discarded.", 261 | "confirm discard all 2": "{0}\n\nThis is IRREVERSIBLE, your current working set will be FOREVER LOST.", 262 | "yes discard tracked": "Discard 1 Tracked File", 263 | "yes discard tracked multiple": "Discard {0} Tracked Files", 264 | "discardAll": "Discard All {0} Files", 265 | "confirm discard all single": "Are you sure you want to discard changes in {0}?", 266 | "confirm discard all": "Are you sure you want to discard ALL changes in {0} files?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST if you proceed.", 267 | "discardAll multiple": "Discard 1 File", 268 | "confirm delete multiple": "Are you sure you want to DELETE {0} files?\nThis is IRREVERSIBLE!\nThese files will be FOREVER LOST if you proceed.", 269 | "commit message": "Commit message", 270 | "provide commit message": "Please provide a commit message", 271 | "commit success": "Commit to {0} success \n Whether to go to {1} to create a PR", 272 | "commit failed": "Commit failed", 273 | "confirm stage files with merge conflicts": "Are you sure you want to stage {0} files with merge conflicts", 274 | "confirm stage file with merge conflicts": "Are you sure you want to stage {0} with merge conflicts?" 275 | }, 276 | "pkgNlsJSON": { 277 | "zh-CN": { 278 | "common.cancel": "取消", 279 | "common.certain": "确定", 280 | "common.toplatform": "提交成功,是否去 {0} 创建 PR", 281 | "alex.git.refresh": "git refresh", 282 | "alex.git.inputBox.placeholder": "消息({0} 直接提交并推送) ", 283 | "changes": "更改", 284 | "staged.changes": "暂存的更改", 285 | "merge.changes": "合并的更改", 286 | "untracked.changes": "未追踪的更改", 287 | "git.title.workingTree": "{0} (工作树)", 288 | "git.title.untracked": "{0} (未追踪)", 289 | "git.title.deleted": "{0} (已删除)", 290 | "git.title.added": "{0} (新增)", 291 | "git error details": "Git Error", 292 | "deleted": "已删除", 293 | "added": "新增", 294 | "modified": "修改", 295 | "diff": "更改", 296 | "discard": "放弃更改", 297 | "confirm delete": "确定要删除 {0} 吗?", 298 | "restore file": "恢复文件", 299 | "confirm restore": "确认是否还原 {0}", 300 | "confirm discard": "确定要放弃 {0} 中更改吗?", 301 | "delete file": "删除文件", 302 | "delete files": "删除文件", 303 | "restore files": "还原文件", 304 | "confirm restore multiple": "确认还原 {0} 个文件?", 305 | "confirm discard multiple": "确认放弃在 {0} 个文件中的更改?", 306 | "warn untracked": "确认删除 {0} 个文件!\nT此操作不可撤消!文件将被永久删除。", 307 | "there are untracked files single": "若放弃下面未跟踪的文件,其将被从硬盘上删除: {0}。", 308 | "there are untracked files": "若放弃 {0} 个未跟踪的文件,其将被从硬盘上删除。", 309 | "confirm discard all 2": "{0}\n\n此操作不可撤销,你当前的工作集将会永远丢失。", 310 | "yes discard tracked": "放弃1个文件", 311 | "yes discard tracked multiple": "放弃 {0} 个追踪的文件", 312 | "discardAll": "放弃 所有 {0} 个文件", 313 | "confirm discard all single": "确定放弃 {0} 中的更改吗?", 314 | "confirm discard all": "确定要放弃在 {0} 个文件中的所有更改吗?此操作不可撤销!你当前的工作集将会永远丢失。", 315 | "discardAll multiple": "放弃1个文件", 316 | "confirm delete multiple": "确认删除 {0} 个文件!\nT此操作不可撤消!文件将被永久删除。", 317 | "commit message": "提交信息", 318 | "provide commit message": "请输入提交信息", 319 | "commit success": "向分支 {0} 提交成功", 320 | "commit failed": "提交失败", 321 | "confirm stage files with merge conflicts": "确定要暂存含有合并冲突的 {0} 个文件吗?", 322 | "confirm stage file with merge conflicts": "确定要暂存含有合并冲突的 {0} 吗?" 323 | } 324 | }, 325 | "nlsList": [ 326 | { 327 | "filename": "package.nls.zh-cn.json", 328 | "languageId": ".zh-cn" 329 | } 330 | ], 331 | "extendConfig": {}, 332 | "webAssets": [ 333 | "package.json", 334 | "out/worker/index.js" 335 | ], 336 | "mode": "public" 337 | } 338 | -------------------------------------------------------------------------------- /search.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | import { AppRenderer, requireModule, WORKSPACE_ROOT } from '@codeblitzjs/ide-core'; 5 | import '@codeblitzjs/ide-core/bundle/codeblitz.css'; 6 | import '@codeblitzjs/ide-core/languages'; 7 | 8 | let zipData: Buffer; 9 | 10 | const zipDataPromise = (async () => { 11 | const res = await fetch( 12 | 'http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/green-trail-test/dc85f34d-2467-436b-a0fe-092133ead0d6/demo.zip' 13 | ); 14 | const buf = await res.arrayBuffer(); 15 | zipData = Buffer.from(new Uint8Array(buf)); 16 | })(); 17 | 18 | const fse = requireModule('fs-extra') 19 | const path = requireModule('path') 20 | 21 | const workspaceDir = 'log' 22 | const workspaceFolder = path.join(WORKSPACE_ROOT, workspaceDir) 23 | 24 | const walkWorkspace = async (dir: string, callback: (filepath: string) => Promise) => { 25 | const filenames: string[] = await fse.readdir(dir) 26 | let finish = false 27 | for (const filename of filenames) { 28 | const filepath = path.join(dir, filename) 29 | const stat = await fse.stat(filepath) 30 | if (stat.isDirectory()) { 31 | await walkWorkspace(filepath, callback) 32 | } else { 33 | // 排除一些非文本文件 34 | if (!/\.(zip|jpg|png)$/.test(filepath)) { 35 | finish = await callback(filepath) 36 | if (finish) break 37 | } 38 | } 39 | } 40 | } 41 | 42 | const App = () => { 43 | return ( 44 |
45 | { 66 | let maxResults = 0 67 | return walkWorkspace(workspaceFolder, async (filepath) => { 68 | if (maxResults > options.maxResults) return true 69 | const content = await fse.readFile(filepath, 'utf8') 70 | if (!content) return false; 71 | const lines: string[] = content.split(/\r\n|\r|\n/g) 72 | lines.forEach((line, index) => { 73 | if (!line) return 74 | if (maxResults > options.maxResults) return 75 | const matches: [number, number][] = [] 76 | if (query.isRegExp) { 77 | const regexp = new RegExp(query.pattern, `g${query.isCaseSensitive ? '' : 'i'}`) 78 | let execArray: RegExpExecArray | null = null 79 | while(execArray = regexp.exec(line)) { 80 | if (!execArray) return 81 | matches.push([ execArray.index, execArray.index + execArray[0].length ]) 82 | } 83 | if (matches.length) { 84 | progress.report({ 85 | path: path.relative(workspaceFolder, filepath), 86 | lineNumber: index + 1, 87 | preview: { 88 | text: line, 89 | matches, 90 | }, 91 | }) 92 | maxResults++ 93 | } 94 | } else { 95 | const text = query.isCaseSensitive ? line : line.toLowerCase(); 96 | const search = query.isCaseSensitive ? query.pattern : query.pattern.toLowerCase() 97 | let lastMatchIndex = -search.length; 98 | while ( 99 | (lastMatchIndex = text.indexOf(search, lastMatchIndex + search.length)) !== -1 100 | ) { 101 | matches.push([lastMatchIndex, lastMatchIndex + search.length]); 102 | } 103 | if (matches.length) { 104 | progress.report({ 105 | path: path.relative(workspaceFolder, filepath), 106 | lineNumber: index + 1, 107 | preview: { 108 | text: line, 109 | matches, 110 | }, 111 | }); 112 | maxResults++ 113 | } 114 | } 115 | }) 116 | return maxResults >= options.maxResults 117 | }) 118 | } 119 | } 120 | }} 121 | /> 122 |
123 | ); 124 | }; 125 | 126 | zipDataPromise.then(() => { 127 | ReactDOM.createRoot(document.getElementById('main')!).render(); 128 | }); 129 | -------------------------------------------------------------------------------- /startup-bundle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | import { AppRenderer, BrowserFSFileType as FileType, requireModule } from '@codeblitzjs/ide-core/bundle/codeblitz' 5 | 6 | import '@codeblitzjs/ide-core/bundle/codeblitz.css'; 7 | import '@codeblitzjs/ide-core/languages'; 8 | import WorkerExample from './extensions/worker-example/worker-example'; 9 | 10 | const { Autowired, Injectable } = requireModule('@opensumi/di'); 11 | const { IEventBus, BrowserModule, Domain, ClientAppContribution, Disposable } = requireModule('@opensumi/ide-core-browser'); 12 | const { EditorDocumentModelWillSaveEvent, IEditorDocumentModelService } = requireModule('@opensumi/ide-editor'); 13 | 14 | const dirMap: Record = { 15 | '/': [ 16 | ['doc', FileType.DIRECTORY], 17 | ['hello.html', FileType.FILE], 18 | ['hello.js', FileType.FILE], 19 | ['hello.py', FileType.FILE], 20 | ['Hello.java', FileType.FILE], 21 | ['hello.go', FileType.FILE], 22 | ['appveyor.yml', FileType.FILE], 23 | ['test.yaml', FileType.FILE], 24 | 25 | ], 26 | '/doc': [ 27 | ['README.md', FileType.FILE], 28 | ], 29 | }; 30 | 31 | const fileMap = { 32 | '/doc/README.md': '# codeblitz-startup\n> codeblitz demo', 33 | '/hello.html': ` 34 | 35 | 36 | 37 |

Hello, World!

38 | 39 | 40 | `.trimStart(), 41 | '/hello.js': 'console.log("Hello, World!");', 42 | '/hello.py': 'print("Hello, World!")', 43 | '/Hello.java': ` 44 | public class Hello { 45 | public static void main(String[] args) { 46 | System.out.println("Hello, World!"); 47 | } 48 | } 49 | `.trimStart(), 50 | '/hello.go': ` 51 | package main 52 | 53 | import "fmt" 54 | 55 | func main() { 56 | fmt.Println("Hello, World!") 57 | } 58 | `.trimStart(), 59 | '/appveyor.yml': `# version format 60 | version: 1.0.{build} 61 | 62 | # you can use {branch} name in version format too 63 | # version: 1.0.{build}-{branch} 64 | 65 | # branches to build 66 | branches: 67 | # whitelist 68 | only: 69 | - master 70 | - production 71 | 72 | # blacklist 73 | except: 74 | - gh-pages 75 | 76 | # Do not build on tags (GitHub, Bitbucket, GitLab, Gitea) 77 | skip_tags: true 78 | 79 | # Start builds on tags only (GitHub, BitBucket, GitLab, Gitea) 80 | skip_non_tags: true`.trimStart() 81 | } 82 | 83 | 84 | @Domain(ClientAppContribution) 85 | class DocumentSaverContribution extends Disposable implements ClientAppContribution { 86 | @Autowired(IEventBus) 87 | eventBus: IEventBus; 88 | 89 | @Autowired(IEditorDocumentModelService) 90 | documentService: IEditorDocumentModelService; 91 | 92 | onDidStart() { 93 | this.addDispose( 94 | this.eventBus.on(EditorDocumentModelWillSaveEvent, (event) => { 95 | console.log("Document will save", event.payload); 96 | const docModel = this.documentService.getModelReference(event.payload.uri); 97 | if (!docModel) return; 98 | 99 | const fullText = docModel.instance.getText(); 100 | // now we can do something with the text 101 | console.log('First five characters of the document are: ', fullText.substr(0, 5)); 102 | }) 103 | ) 104 | } 105 | } 106 | 107 | 108 | @Injectable() 109 | class CustomModule extends BrowserModule { 110 | providers = [DocumentSaverContribution]; 111 | } 112 | 113 | const App = () => { 114 | return ( 115 | 163 | ) 164 | } 165 | 166 | ReactDOM.createRoot(document.getElementById('main')!).render(); 167 | -------------------------------------------------------------------------------- /startup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | import { AppRenderer, BrowserFSFileType as FileType, IAppInstance } from '@codeblitzjs/ide-core' 5 | import { IEventBus, BrowserModule, Domain, ClientAppContribution, Disposable } from '@opensumi/ide-core-browser'; 6 | import { EditorDocumentModelWillSaveEvent, IEditorDocumentModelService } from '@opensumi/ide-editor/lib/browser'; 7 | 8 | import '@codeblitzjs/ide-core/bundle/codeblitz.css'; 9 | import '@codeblitzjs/ide-core/languages' 10 | import WorkerExample from './extensions/worker-example/worker-example' 11 | import { Autowired, Injectable } from '@opensumi/di'; 12 | 13 | const dirMap: Record = { 14 | '/': [ 15 | ['doc', FileType.DIRECTORY], 16 | ['hello.html', FileType.FILE], 17 | ['hello.js', FileType.FILE], 18 | ['hello.py', FileType.FILE], 19 | ['Hello.java', FileType.FILE], 20 | ['hello.go', FileType.FILE], 21 | ['appveyor.yml', FileType.FILE], 22 | ['test.yaml', FileType.FILE], 23 | 24 | ], 25 | '/doc': [ 26 | ['README.md', FileType.FILE], 27 | ], 28 | }; 29 | 30 | const fileMap = { 31 | '/doc/README.md': '# codeblitz-startup\n> codeblitz demo', 32 | '/hello.html': ` 33 | 34 | 35 | 36 |

Hello, World!

37 | 38 | 39 | `.trimStart(), 40 | '/hello.js': 'console.log("Hello, World!");', 41 | '/hello.py': 'print("Hello, World!")', 42 | '/Hello.java': ` 43 | public class Hello { 44 | public static void main(String[] args) { 45 | System.out.println("Hello, World!"); 46 | } 47 | } 48 | `.trimStart(), 49 | '/hello.go': ` 50 | package main 51 | 52 | import "fmt" 53 | 54 | func main() { 55 | fmt.Println("Hello, World!") 56 | } 57 | `.trimStart(), 58 | '/appveyor.yml': `# version format 59 | version: 1.0.{build} 60 | 61 | # you can use {branch} name in version format too 62 | # version: 1.0.{build}-{branch} 63 | 64 | # branches to build 65 | branches: 66 | # whitelist 67 | only: 68 | - master 69 | - production 70 | 71 | # blacklist 72 | except: 73 | - gh-pages 74 | 75 | # Do not build on tags (GitHub, Bitbucket, GitLab, Gitea) 76 | skip_tags: true 77 | 78 | # Start builds on tags only (GitHub, BitBucket, GitLab, Gitea) 79 | skip_non_tags: true`.trimStart() 80 | } 81 | 82 | 83 | @Domain(ClientAppContribution) 84 | class DocumentSaverContribution extends Disposable implements ClientAppContribution { 85 | @Autowired(IEventBus) 86 | eventBus: IEventBus; 87 | 88 | @Autowired(IEditorDocumentModelService) 89 | documentService: IEditorDocumentModelService; 90 | 91 | onDidStart() { 92 | this.addDispose( 93 | this.eventBus.on(EditorDocumentModelWillSaveEvent, (event) => { 94 | console.log("Document will save", event.payload); 95 | const docModel = this.documentService.getModelReference(event.payload.uri); 96 | if (!docModel) return; 97 | 98 | const fullText = docModel.instance.getText(); 99 | // now we can do something with the text 100 | console.log('First five characters of the document are: ', fullText.substr(0, 5)); 101 | }) 102 | ) 103 | } 104 | } 105 | 106 | 107 | @Injectable() 108 | class CustomModule extends BrowserModule { 109 | providers = [DocumentSaverContribution]; 110 | } 111 | 112 | const App = () => { 113 | return ( 114 | 162 | ) 163 | } 164 | 165 | ReactDOM.createRoot(document.getElementById('main')!).render(); 166 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2015", 4 | "target": "ES2017", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "strictPropertyInitialization": false, 8 | "sourceMap": true, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "importHelpers": true, 12 | "resolveJsonModule": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "downlevelIteration": true, 16 | "noEmitOnError": false, 17 | "noImplicitAny": false, 18 | "skipLibCheck": false, 19 | "strictFunctionTypes": false, 20 | "jsx": "react", 21 | "baseUrl": ".", 22 | "rootDir": ".", 23 | "outDir": "dist", 24 | "noUnusedLocals": false, 25 | "allowSyntheticDefaultImports": true, 26 | "esModuleInterop": true, 27 | "declaration": true, 28 | "declarationMap": true, 29 | "lib": [ 30 | "DOM", 31 | "ES2015", 32 | "ES2016", 33 | "ES2017" 34 | ], 35 | "typeRoots": [ 36 | "./node_modules/@types" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | require("dotenv").config({ path: path.join(__dirname, "./.env") }); 4 | const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); 5 | 6 | const webpack = require('webpack'); 7 | 8 | module.exports = (env) => ({ 9 | entry: path.join(__dirname, env.entry || "startup"), 10 | output: { 11 | filename: "[name].js", 12 | path: path.resolve(__dirname, `dist`), 13 | publicPath: "/", 14 | }, 15 | devtool: "inline-source-map", 16 | mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', 17 | resolve: { 18 | extensions: [".ts", ".tsx", ".js", ".json", ".less"], 19 | fallback: { 20 | "path": require.resolve("path-browserify"), 21 | "fs": false, 22 | "crypto": false, 23 | "stream": false, 24 | "buffer": false, 25 | "os": false, 26 | "process": false, 27 | } 28 | }, 29 | experiments: { 30 | asyncWebAssembly: true, 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.tsx?$/, 36 | use: [ 37 | { 38 | loader: "ts-loader", 39 | options: { 40 | transpileOnly: true, 41 | }, 42 | }, 43 | ], 44 | }, 45 | { 46 | test: /\.css$/, 47 | use: ["style-loader", "css-loader"], 48 | }, 49 | { 50 | test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/, 51 | use: [ 52 | { 53 | loader: "file-loader", 54 | options: { 55 | name: "[name].[ext]", 56 | esModule: false, 57 | publicPath: "./", 58 | }, 59 | }, 60 | ], 61 | }, 62 | { 63 | test: /\.module.less$/, 64 | use: [ 65 | { 66 | loader: "style-loader", 67 | options: { 68 | esModule: false, 69 | }, 70 | }, 71 | { 72 | loader: "css-loader", 73 | options: { 74 | importLoaders: 1, 75 | sourceMap: true, 76 | esModule: false, 77 | modules: { 78 | mode: "local", 79 | localIdentName: "[local]___[hash:base64:5]", 80 | }, 81 | }, 82 | }, 83 | { 84 | loader: "less-loader", 85 | options: { 86 | lessOptions: { 87 | javascriptEnabled: true, 88 | }, 89 | }, 90 | }, 91 | ], 92 | }, 93 | { 94 | test: /^((?!\.module).)*less$/, 95 | use: [ 96 | { 97 | loader: "style-loader", 98 | options: { 99 | esModule: false, 100 | }, 101 | }, 102 | { 103 | loader: "css-loader", 104 | options: { 105 | importLoaders: 1, 106 | sourceMap: true, 107 | esModule: false, 108 | }, 109 | }, 110 | { 111 | loader: "less-loader", 112 | options: { 113 | lessOptions: { 114 | javascriptEnabled: true, 115 | modifyVars: { 116 | "kt-html-selector": "alex-root", 117 | "kt-body-selector": "alex-root", 118 | }, 119 | }, 120 | }, 121 | }, 122 | ], 123 | }, 124 | { 125 | test: /\.(png|jpe?g|gif|webp|ico|svg)(\?.*)?$/, 126 | use: [ 127 | { 128 | loader: "url-loader", 129 | options: { 130 | limit: 10000, 131 | name: "[name].[ext]", 132 | // require 图片的时候不用加 .default 133 | esModule: false, 134 | fallback: { 135 | loader: "file-loader", 136 | options: { 137 | name: "[name].[ext]", 138 | esModule: false, 139 | }, 140 | }, 141 | }, 142 | }, 143 | ], 144 | }, 145 | { 146 | test: /\.(txt|text|md)$/, 147 | use: "raw-loader", 148 | }, 149 | ], 150 | }, 151 | plugins: [ 152 | new HtmlWebpackPlugin({ 153 | filename: `index.html`, 154 | template: path.join(__dirname, "./index.html"), 155 | }), 156 | new webpack.DefinePlugin({ 157 | 'process.env': { 158 | HOST: JSON.stringify(process.env.HOST || ''), 159 | }, 160 | }), 161 | new NodePolyfillPlugin({ 162 | includeAliases: ['process', 'Buffer'], 163 | }), 164 | ], 165 | devServer: { 166 | static: { 167 | directory: path.join(__dirname, 'assets'), 168 | }, 169 | allowedHosts: "all", 170 | host: "0.0.0.0", 171 | port: 8001, 172 | historyApiFallback: { 173 | disableDotRule: true, 174 | }, 175 | hot: true, 176 | client: { 177 | overlay: { 178 | errors: true, 179 | warnings: false, 180 | runtimeErrors: false, 181 | }, 182 | }, 183 | }, 184 | }); 185 | -------------------------------------------------------------------------------- /zip.tsx: -------------------------------------------------------------------------------- 1 | // 接收zip文件 保存后在输出zip 2 | import React, { useState, useMemo } from 'react'; 3 | import ReactDOM from 'react-dom/client'; 4 | import { AppRenderer } from '@codeblitzjs/ide-core'; 5 | import '@codeblitzjs/ide-core/bundle/codeblitz.css'; 6 | import '@codeblitzjs/ide-core/languages'; 7 | import typescript from '@codeblitzjs/ide-core/extensions/codeblitz.typescript-language-features-worker'; 8 | import { DOWNLOAD_ZIP, getDefaultLayoutConfig, RegisterZipMenuModule } from './modules/registerZipMenu'; 9 | import ZipPlugin from './common/zipPlugin'; 10 | 11 | let zipData: Buffer; 12 | 13 | const zipDataPromise = (async () => { 14 | // 请求需开启本地静态服务器 15 | // cd ./assets 16 | // npx serve . 17 | const res = await fetch( 18 | // localhost:3000 19 | 'http://btt-dw24vyoob7cntf7tfewspw7ooi.hon.alibaba-inc.com/compile.zip' 20 | ); 21 | // const res = await fetch( 22 | // 'http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/green-trail-test/dc85f34d-2467-436b-a0fe-092133ead0d6/demo.zip' 23 | // ); 24 | const buf = await res.arrayBuffer(); 25 | zipData = Buffer.from(new Uint8Array(buf)); 26 | })(); 27 | 28 | export const FullScreenToken = Symbol('FullScreenToken'); 29 | 30 | 31 | const App = () => { 32 | 33 | const [isFullScreen, setFullscreen] = useState(false); 34 | 35 | const zipPlugin = useMemo(() => { 36 | return new ZipPlugin( 37 | () => setFullscreen(true), 38 | () => setFullscreen(false), 39 | isFullScreen 40 | ); 41 | }, []); 42 | 43 | const downloadZip = () => { 44 | zipPlugin.commands?.executeCommand(DOWNLOAD_ZIP) 45 | } 46 | return ( 47 |
48 |
49 | 50 |
51 |
58 | { 61 | window.app = app; 62 | // app.injector.addProviders({ 63 | // token: FullScreenToken, 64 | // useFactory: (inject) => { 65 | // setFullscreen(!isFullScreen); 66 | // }, 67 | // }) 68 | }} 69 | appConfig={{ 70 | workspaceDir: 'zip_file_system', 71 | defaultPreferences: { 72 | 'general.theme': 'opensumi-design-light-theme', 73 | 'editor.guides.bracketPairs': false, 74 | }, 75 | layoutConfig: getDefaultLayoutConfig(), 76 | extensionMetadata: [ 77 | typescript 78 | ], 79 | modules: [RegisterZipMenuModule], 80 | plugins: [zipPlugin] 81 | }} 82 | runtimeConfig={{ 83 | workspace: { 84 | filesystem: { 85 | fs: 'OverlayFS', 86 | options: { 87 | writable: { 88 | fs: 'IndexedDB', 89 | options: { 90 | storeName: 'zipFS', 91 | }, 92 | }, 93 | readable: { 94 | fs: 'ZipFS', 95 | options: { 96 | zipData, 97 | }, 98 | }, 99 | }, 100 | } 101 | }, 102 | }} 103 | /> 104 |
105 |
106 | ); 107 | }; 108 | 109 | zipDataPromise.then(() => { 110 | ReactDOM.createRoot(document.getElementById('main')!).render(); 111 | }); 112 | --------------------------------------------------------------------------------