@talex-touch/utils 1.0.18 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/channel/index.ts +49 -1
- package/common/index.ts +2 -0
- package/common/search/gather.ts +45 -0
- package/common/search/index.ts +67 -0
- package/common/storage/constants.ts +16 -2
- package/common/storage/entity/index.ts +2 -1
- package/common/storage/entity/openers.ts +32 -0
- package/common/storage/entity/shortcut-settings.ts +22 -0
- package/common/storage/shortcut-storage.ts +58 -0
- package/common/utils/file.ts +62 -0
- package/common/{utils.ts → utils/index.ts} +14 -2
- package/common/utils/polling.ts +184 -0
- package/common/utils/task-queue.ts +108 -0
- package/common/utils/time.ts +374 -0
- package/core-box/README.md +8 -8
- package/core-box/builder/index.ts +6 -0
- package/core-box/builder/tuff-builder.example.ts.bak +258 -0
- package/core-box/builder/tuff-builder.ts +1162 -0
- package/core-box/index.ts +5 -2
- package/core-box/run-tests.sh +7 -0
- package/core-box/search.ts +1 -536
- package/core-box/tuff/index.ts +6 -0
- package/core-box/tuff/tuff-dsl.ts +1412 -0
- package/electron/clipboard-helper.ts +199 -0
- package/electron/env-tool.ts +36 -2
- package/electron/file-parsers/index.ts +8 -0
- package/electron/file-parsers/parsers/text-parser.ts +109 -0
- package/electron/file-parsers/registry.ts +92 -0
- package/electron/file-parsers/types.ts +58 -0
- package/electron/index.ts +3 -0
- package/eventbus/index.ts +0 -7
- package/index.ts +3 -1
- package/package.json +4 -29
- package/plugin/channel.ts +48 -16
- package/plugin/index.ts +194 -30
- package/plugin/log/types.ts +11 -0
- package/plugin/node/index.ts +4 -0
- package/plugin/node/logger-manager.ts +113 -0
- package/plugin/{log → node}/logger.ts +41 -7
- package/plugin/plugin-source.ts +74 -0
- package/plugin/preload.ts +5 -15
- package/plugin/providers/index.ts +2 -0
- package/plugin/providers/registry.ts +47 -0
- package/plugin/providers/types.ts +54 -0
- package/plugin/risk/index.ts +1 -0
- package/plugin/risk/types.ts +20 -0
- package/plugin/sdk/enum/bridge-event.ts +4 -0
- package/plugin/sdk/enum/index.ts +1 -0
- package/plugin/sdk/hooks/bridge.ts +68 -0
- package/plugin/sdk/hooks/index.ts +2 -1
- package/plugin/sdk/hooks/life-cycle.ts +2 -4
- package/plugin/sdk/index.ts +2 -0
- package/plugin/sdk/storage.ts +84 -0
- package/plugin/sdk/types.ts +2 -2
- package/plugin/sdk/window/index.ts +5 -3
- package/preload/index.ts +2 -0
- package/preload/loading.ts +15 -0
- package/preload/renderer.ts +41 -0
- package/renderer/hooks/arg-mapper.ts +79 -0
- package/renderer/hooks/index.ts +2 -0
- package/renderer/hooks/initialize.ts +198 -0
- package/renderer/index.ts +3 -0
- package/renderer/storage/app-settings.ts +2 -0
- package/renderer/storage/base-storage.ts +1 -0
- package/renderer/storage/openers.ts +11 -0
- package/renderer/touch-sdk/env.ts +106 -0
- package/renderer/touch-sdk/index.ts +108 -0
- package/renderer/touch-sdk/terminal.ts +85 -0
- package/renderer/touch-sdk/utils.ts +61 -0
- package/search/levenshtein-utils.ts +39 -0
- package/search/types.ts +16 -16
- package/types/index.ts +2 -1
- package/types/modules/base.ts +146 -0
- package/types/modules/index.ts +4 -0
- package/types/modules/module-lifecycle.ts +148 -0
- package/types/modules/module-manager.ts +99 -0
- package/types/modules/module.ts +112 -0
- package/types/touch-app-core.ts +16 -93
- package/core-box/types.ts +0 -384
- package/plugin/log/logger-manager.ts +0 -60
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for renderer initialization information
|
|
3
|
+
* @interface IInitializationInfo
|
|
4
|
+
*/
|
|
5
|
+
export interface IInitializationInfo {
|
|
6
|
+
/** Timestamp when the renderer process was initialized */
|
|
7
|
+
initTimestamp: number
|
|
8
|
+
/** ISO string of initialization time */
|
|
9
|
+
initTime: string
|
|
10
|
+
/** User agent string */
|
|
11
|
+
userAgent: string
|
|
12
|
+
/** Current URL of the renderer */
|
|
13
|
+
currentUrl: string
|
|
14
|
+
/** Operating system platform */
|
|
15
|
+
platform: NodeJS.Platform
|
|
16
|
+
/** Screen resolution information */
|
|
17
|
+
screenResolution: {
|
|
18
|
+
width: number
|
|
19
|
+
height: number
|
|
20
|
+
pixelRatio: number
|
|
21
|
+
}
|
|
22
|
+
/** Window size information */
|
|
23
|
+
windowSize: {
|
|
24
|
+
innerWidth: number
|
|
25
|
+
innerHeight: number
|
|
26
|
+
outerWidth: number
|
|
27
|
+
outerHeight: number
|
|
28
|
+
}
|
|
29
|
+
/** Performance timing information */
|
|
30
|
+
performance: {
|
|
31
|
+
navigationStart: number
|
|
32
|
+
loadEventEnd?: number
|
|
33
|
+
domContentLoadedEventEnd?: number
|
|
34
|
+
}
|
|
35
|
+
/** Browser/renderer capabilities */
|
|
36
|
+
capabilities: {
|
|
37
|
+
webgl: boolean
|
|
38
|
+
webgl2: boolean
|
|
39
|
+
webAudio: boolean
|
|
40
|
+
serviceWorker: boolean
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare global {
|
|
45
|
+
export interface Window {
|
|
46
|
+
/** Global initialization information cache */
|
|
47
|
+
$initInfo: IInitializationInfo
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Checks WebGL support
|
|
53
|
+
* @returns True if WebGL is supported
|
|
54
|
+
*/
|
|
55
|
+
function checkWebGLSupport(): boolean {
|
|
56
|
+
try {
|
|
57
|
+
const canvas = document.createElement('canvas')
|
|
58
|
+
return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))
|
|
59
|
+
} catch {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Checks WebGL2 support
|
|
66
|
+
* @returns True if WebGL2 is supported
|
|
67
|
+
*/
|
|
68
|
+
function checkWebGL2Support(): boolean {
|
|
69
|
+
try {
|
|
70
|
+
const canvas = document.createElement('canvas')
|
|
71
|
+
return !!canvas.getContext('webgl2')
|
|
72
|
+
} catch {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Checks Web Audio API support
|
|
79
|
+
* @returns True if Web Audio API is supported
|
|
80
|
+
*/
|
|
81
|
+
function checkWebAudioSupport(): boolean {
|
|
82
|
+
return 'AudioContext' in window || 'webkitAudioContext' in window
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks Service Worker support
|
|
87
|
+
* @returns True if Service Worker is supported
|
|
88
|
+
*/
|
|
89
|
+
function checkServiceWorkerSupport(): boolean {
|
|
90
|
+
return 'serviceWorker' in navigator
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Initializes renderer process with system and performance information
|
|
95
|
+
* @returns Initialization information object
|
|
96
|
+
*/
|
|
97
|
+
export function useInitialize(): IInitializationInfo {
|
|
98
|
+
if (window.$initInfo) {
|
|
99
|
+
return window.$initInfo
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const now = Date.now()
|
|
103
|
+
const initInfo: IInitializationInfo = {
|
|
104
|
+
initTimestamp: now,
|
|
105
|
+
initTime: new Date(now).toISOString(),
|
|
106
|
+
userAgent: navigator.userAgent,
|
|
107
|
+
currentUrl: window.location.href,
|
|
108
|
+
platform: process.platform,
|
|
109
|
+
screenResolution: {
|
|
110
|
+
width: screen.width,
|
|
111
|
+
height: screen.height,
|
|
112
|
+
pixelRatio: window.devicePixelRatio || 1
|
|
113
|
+
},
|
|
114
|
+
windowSize: {
|
|
115
|
+
innerWidth: window.innerWidth,
|
|
116
|
+
innerHeight: window.innerHeight,
|
|
117
|
+
outerWidth: window.outerWidth,
|
|
118
|
+
outerHeight: window.outerHeight
|
|
119
|
+
},
|
|
120
|
+
performance: {
|
|
121
|
+
navigationStart: performance.timeOrigin || now,
|
|
122
|
+
loadEventEnd: (performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming)?.loadEventEnd || undefined,
|
|
123
|
+
domContentLoadedEventEnd: (performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming)?.domContentLoadedEventEnd || undefined
|
|
124
|
+
},
|
|
125
|
+
capabilities: {
|
|
126
|
+
webgl: checkWebGLSupport(),
|
|
127
|
+
webgl2: checkWebGL2Support(),
|
|
128
|
+
webAudio: checkWebAudioSupport(),
|
|
129
|
+
serviceWorker: checkServiceWorkerSupport()
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return window.$initInfo = initInfo
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Gets the initialization timestamp
|
|
138
|
+
* @returns Timestamp when the renderer was initialized
|
|
139
|
+
*/
|
|
140
|
+
export function getInitTimestamp(): number {
|
|
141
|
+
return useInitialize().initTimestamp
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Gets the initialization time as ISO string
|
|
146
|
+
* @returns ISO string of initialization time
|
|
147
|
+
*/
|
|
148
|
+
export function getInitTime(): string {
|
|
149
|
+
return useInitialize().initTime
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Gets the time elapsed since initialization in milliseconds
|
|
154
|
+
* @returns Time elapsed since initialization
|
|
155
|
+
*/
|
|
156
|
+
export function getTimeElapsed(): number {
|
|
157
|
+
return Date.now() - getInitTimestamp()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Gets screen resolution information
|
|
162
|
+
* @returns Screen resolution object
|
|
163
|
+
*/
|
|
164
|
+
export function getScreenInfo() {
|
|
165
|
+
return useInitialize().screenResolution
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Gets window size information
|
|
170
|
+
* @returns Window size object
|
|
171
|
+
*/
|
|
172
|
+
export function getWindowInfo() {
|
|
173
|
+
return useInitialize().windowSize
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Gets renderer capabilities
|
|
178
|
+
* @returns Capabilities object
|
|
179
|
+
*/
|
|
180
|
+
export function getCapabilities() {
|
|
181
|
+
return useInitialize().capabilities
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Refreshes performance timing information
|
|
186
|
+
* @returns Updated initialization info
|
|
187
|
+
*/
|
|
188
|
+
export function refreshPerformanceInfo(): IInitializationInfo {
|
|
189
|
+
if (window.$initInfo) {
|
|
190
|
+
const navigationEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
|
|
191
|
+
window.$initInfo.performance = {
|
|
192
|
+
navigationStart: performance.timeOrigin || window.$initInfo.performance.navigationStart,
|
|
193
|
+
loadEventEnd: navigationEntry?.loadEventEnd || undefined,
|
|
194
|
+
domContentLoadedEventEnd: navigationEntry?.domContentLoadedEventEnd || undefined
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return window.$initInfo
|
|
198
|
+
}
|
package/renderer/index.ts
CHANGED
|
@@ -327,6 +327,7 @@ export class TouchStorage<T extends object> {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
const result = channel.sendSync('storage:get', this.#qualifiedName)
|
|
330
|
+
console.log("result", result)
|
|
330
331
|
const parsed = result ? (result as Partial<T>) : {};
|
|
331
332
|
this.assignData(parsed, true);
|
|
332
333
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TouchStorage } from '.'
|
|
2
|
+
import { openersOriginData, StorageList, type OpenersMap } from '../..'
|
|
3
|
+
|
|
4
|
+
class OpenersStorage extends TouchStorage<OpenersMap> {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(StorageList.OPENERS, JSON.parse(JSON.stringify(openersOriginData)))
|
|
7
|
+
this.setAutoSave(true)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const openersStorage = new OpenersStorage()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Terminal } from './terminal'
|
|
2
|
+
import { ITouchClientChannel } from '@talex-touch/utils/channel'
|
|
3
|
+
|
|
4
|
+
export class EnvDetector {
|
|
5
|
+
private static channel: ITouchClientChannel;
|
|
6
|
+
|
|
7
|
+
public static init(channel: ITouchClientChannel) {
|
|
8
|
+
this.channel = channel;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private static async checkCommand(
|
|
12
|
+
command: string,
|
|
13
|
+
versionArgs: string = '--version',
|
|
14
|
+
versionRegex: RegExp = /(\d+\.\d+\.\d+)/
|
|
15
|
+
): Promise<string | false> {
|
|
16
|
+
if (!this.channel) {
|
|
17
|
+
throw new Error("EnvDetector not initialized. Call EnvDetector.init(channel) first.");
|
|
18
|
+
}
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const terminal = new Terminal(this.channel)
|
|
21
|
+
let output = ''
|
|
22
|
+
let resolved = false;
|
|
23
|
+
|
|
24
|
+
const resolveOnce = (value: string | false) => {
|
|
25
|
+
if (!resolved) {
|
|
26
|
+
resolved = true;
|
|
27
|
+
resolve(value);
|
|
28
|
+
// For child_process, there's no explicit disconnect/kill needed after it exits.
|
|
29
|
+
// The process lifecycle is managed by the OS once it's started and exits.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
terminal.onData((data) => {
|
|
34
|
+
output += data
|
|
35
|
+
const match = output.match(versionRegex)
|
|
36
|
+
if (match && match[1]) {
|
|
37
|
+
resolveOnce(match[1])
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
terminal.onExit(() => {
|
|
42
|
+
const match = output.match(versionRegex)
|
|
43
|
+
resolveOnce(match && match[1] ? match[1] : false)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// Execute the command with arguments
|
|
47
|
+
terminal.exec(command, [versionArgs]).catch(() => {
|
|
48
|
+
resolveOnce(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Timeout in case the command hangs or never returns
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
resolveOnce(false)
|
|
54
|
+
}, 2000);
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static async getNode(): Promise<string | false> {
|
|
59
|
+
return this.checkCommand('node')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static async getNpm(): Promise<string | false> {
|
|
63
|
+
return this.checkCommand('npm')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static async getGit(): Promise<string | false> {
|
|
67
|
+
return this.checkCommand('git')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static async getDegit(): Promise<boolean> {
|
|
71
|
+
if (!this.channel) {
|
|
72
|
+
throw new Error("EnvDetector not initialized. Call EnvDetector.init(channel) first.");
|
|
73
|
+
}
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const terminal = new Terminal(this.channel)
|
|
76
|
+
let receivedOutput = false;
|
|
77
|
+
let resolved = false;
|
|
78
|
+
|
|
79
|
+
const resolveOnce = (value: boolean) => {
|
|
80
|
+
if (!resolved) {
|
|
81
|
+
resolved = true;
|
|
82
|
+
resolve(value);
|
|
83
|
+
// For child_process, there's no explicit disconnect/kill needed after it exits.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
terminal.onData(() => {
|
|
88
|
+
receivedOutput = true;
|
|
89
|
+
resolveOnce(true); // As soon as we get any output, we know it's there.
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
terminal.onExit(() => {
|
|
93
|
+
resolveOnce(receivedOutput)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Execute degit with --help
|
|
97
|
+
terminal.exec('degit', ['--help']).catch(() => {
|
|
98
|
+
resolveOnce(false)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
resolveOnce(receivedOutput);
|
|
103
|
+
}, 2000)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ITouchClientChannel } from '@talex-touch/utils/channel'
|
|
2
|
+
|
|
3
|
+
export interface TouchSDKOptions {
|
|
4
|
+
channel: ITouchClientChannel
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface FolderOpenOptions {
|
|
8
|
+
path: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ExecuteCommandOptions {
|
|
12
|
+
command: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AppOpenOptions {
|
|
16
|
+
appName?: string
|
|
17
|
+
path?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ExternalUrlOptions {
|
|
21
|
+
url: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class TouchSDK {
|
|
25
|
+
private channel: ITouchClientChannel
|
|
26
|
+
|
|
27
|
+
constructor(options: TouchSDKOptions) {
|
|
28
|
+
this.channel = options.channel
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* System Operations
|
|
33
|
+
*/
|
|
34
|
+
async closeApp(): Promise<void> {
|
|
35
|
+
return this.channel.send('close')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async hideApp(): Promise<void> {
|
|
39
|
+
return this.channel.send('hide')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async minimizeApp(): Promise<void> {
|
|
43
|
+
return this.channel.send('minimize')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async openDevTools(): Promise<void> {
|
|
47
|
+
return this.channel.send('dev-tools')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getCurrentWorkingDirectory(): Promise<string> {
|
|
51
|
+
return this.channel.send('common:cwd')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getPackageInfo(): Promise<any> {
|
|
55
|
+
return this.channel.send('get-package')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getOSInfo(): Promise<any> {
|
|
59
|
+
return this.channel.send('get-os')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* File & Folder Operations
|
|
64
|
+
*/
|
|
65
|
+
async openFolder(options: FolderOpenOptions): Promise<void> {
|
|
66
|
+
return this.channel.send('folder:open', options)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async executeCommand(options: ExecuteCommandOptions): Promise<void> {
|
|
70
|
+
return this.channel.send('execute:cmd', options)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async openApp(options: AppOpenOptions): Promise<void> {
|
|
74
|
+
return this.channel.send('app:open', options)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async openExternalUrl(options: ExternalUrlOptions): Promise<void> {
|
|
78
|
+
return this.channel.send('open-external', options)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Plugin Operations
|
|
83
|
+
*/
|
|
84
|
+
async openPluginFolder(pluginName: string): Promise<void> {
|
|
85
|
+
return this.channel.send('plugin:explorer', pluginName)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Module Operations
|
|
90
|
+
*/
|
|
91
|
+
async openModuleFolder(moduleName?: string): Promise<void> {
|
|
92
|
+
return this.channel.send('module:folder', { name: moduleName })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Event Registration
|
|
97
|
+
*/
|
|
98
|
+
onChannelEvent(eventName: string, callback: (data: any) => void): () => void {
|
|
99
|
+
return this.channel.regChannel(eventName, callback)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Raw channel access for advanced usage
|
|
104
|
+
*/
|
|
105
|
+
get rawChannel(): ITouchClientChannel {
|
|
106
|
+
return this.channel
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ITouchClientChannel } from '@talex-touch/utils/channel'
|
|
2
|
+
|
|
3
|
+
type DataCallback = (data: string) => void
|
|
4
|
+
type ExitCallback = (exitCode: number | null) => void
|
|
5
|
+
|
|
6
|
+
export class Terminal {
|
|
7
|
+
private id: string | null = null
|
|
8
|
+
private onDataCallback: DataCallback | null = null
|
|
9
|
+
private onExitCallback: ExitCallback | null = null
|
|
10
|
+
private channel: ITouchClientChannel
|
|
11
|
+
|
|
12
|
+
constructor(channel: ITouchClientChannel) {
|
|
13
|
+
this.channel = channel
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Executes a command and returns the process ID.
|
|
18
|
+
* The command output will be sent via the 'terminal:data' event.
|
|
19
|
+
* The command exit code will be sent via the 'terminal:exit' event.
|
|
20
|
+
* @param command The command to execute.
|
|
21
|
+
* @param args The arguments for the command.
|
|
22
|
+
* @returns A promise that resolves with the process ID.
|
|
23
|
+
*/
|
|
24
|
+
public async exec(command: string, args: string[] = []): Promise<string> {
|
|
25
|
+
// If there's an existing process, it should ideally be killed first.
|
|
26
|
+
// However, for simplicity in this refactor, we'll assume exec is called for a new, independent command.
|
|
27
|
+
// A more robust implementation might track multiple concurrent processes.
|
|
28
|
+
|
|
29
|
+
const { id } = await this.channel.send('terminal:create', { command, args })
|
|
30
|
+
this.id = id
|
|
31
|
+
|
|
32
|
+
// Re-register listeners for the new process ID
|
|
33
|
+
this.channel.regChannel('terminal:data', (channelData) => {
|
|
34
|
+
if (this.id === channelData.data.id && this.onDataCallback) {
|
|
35
|
+
this.onDataCallback(channelData.data.data)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
this.channel.regChannel('terminal:exit', (channelData) => {
|
|
40
|
+
if (this.id === channelData.data.id && this.onExitCallback) {
|
|
41
|
+
this.onExitCallback(channelData.data.exitCode)
|
|
42
|
+
this.id = null
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return id
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Writes data to the process stdin (if applicable).
|
|
51
|
+
* Note: This is less common with child_process for non-interactive commands.
|
|
52
|
+
* @param data The data to write.
|
|
53
|
+
*/
|
|
54
|
+
public write(data: string): void {
|
|
55
|
+
if (this.id) {
|
|
56
|
+
this.channel.send('terminal:write', { id: this.id, data })
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Kills the running process.
|
|
62
|
+
*/
|
|
63
|
+
public kill(): void {
|
|
64
|
+
if (this.id) {
|
|
65
|
+
this.channel.send('terminal:kill', { id: this.id })
|
|
66
|
+
this.id = null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Sets the callback for receiving data from the process.
|
|
72
|
+
* @param callback The callback function.
|
|
73
|
+
*/
|
|
74
|
+
public onData(callback: DataCallback): void {
|
|
75
|
+
this.onDataCallback = callback
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Sets the callback for receiving the exit code from the process.
|
|
80
|
+
* @param callback The callback function.
|
|
81
|
+
*/
|
|
82
|
+
public onExit(callback: ExitCallback): void {
|
|
83
|
+
this.onExitCallback = callback
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { TouchSDK, TouchSDKOptions } from './index'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Factory function to create a TouchSDK instance
|
|
5
|
+
*/
|
|
6
|
+
export function createTouchSDK(options: TouchSDKOptions): TouchSDK {
|
|
7
|
+
return new TouchSDK(options)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Singleton instance for convenience
|
|
12
|
+
*/
|
|
13
|
+
let sdkInstance: TouchSDK | null = null
|
|
14
|
+
|
|
15
|
+
export function initTouchSDK(options: TouchSDKOptions): TouchSDK {
|
|
16
|
+
sdkInstance = new TouchSDK(options)
|
|
17
|
+
return sdkInstance
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getTouchSDK(): TouchSDK {
|
|
21
|
+
if (!sdkInstance) {
|
|
22
|
+
throw new Error('TouchSDK not initialized. Call initTouchSDK first.')
|
|
23
|
+
}
|
|
24
|
+
return sdkInstance
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useTouchSDK(options?: TouchSDKOptions) {
|
|
28
|
+
if (!sdkInstance) {
|
|
29
|
+
if ( !options ) {
|
|
30
|
+
throw new Error('TouchSDK not initialized. Call initTouchSDK first. Cannot use hook here.')
|
|
31
|
+
}
|
|
32
|
+
initTouchSDK(options)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return getTouchSDK()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Utility functions for common operations
|
|
40
|
+
*/
|
|
41
|
+
export const TouchUtils = {
|
|
42
|
+
async openFolder(path: string): Promise<void> {
|
|
43
|
+
return getTouchSDK().openFolder({ path })
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async executeCommand(command: string): Promise<void> {
|
|
47
|
+
return getTouchSDK().executeCommand({ command })
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async openApp(appName: string): Promise<void> {
|
|
51
|
+
return getTouchSDK().openApp({ appName })
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async openUrl(url: string): Promise<void> {
|
|
55
|
+
return getTouchSDK().openExternalUrl({ url })
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async openPluginFolder(pluginName: string): Promise<void> {
|
|
59
|
+
return getTouchSDK().openPluginFolder(pluginName)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes the Levenshtein distance between two strings.
|
|
3
|
+
* This is a standard dynamic programming implementation.
|
|
4
|
+
*
|
|
5
|
+
* @param s1 The first string.
|
|
6
|
+
* @param s2 The second string.
|
|
7
|
+
* @returns The Levenshtein distance.
|
|
8
|
+
*/
|
|
9
|
+
export function levenshteinDistance(s1: string, s2: string): number {
|
|
10
|
+
const m = s1.length;
|
|
11
|
+
const n = s2.length;
|
|
12
|
+
|
|
13
|
+
// Create a 2D array (m+1)x(n+1) to store distances
|
|
14
|
+
const dp: number[][] = Array(m + 1)
|
|
15
|
+
.fill(0)
|
|
16
|
+
.map(() => Array(n + 1).fill(0));
|
|
17
|
+
|
|
18
|
+
// Initialize the DP table
|
|
19
|
+
for (let i = 0; i <= m; i++) {
|
|
20
|
+
dp[i][0] = i;
|
|
21
|
+
}
|
|
22
|
+
for (let j = 0; j <= n; j++) {
|
|
23
|
+
dp[0][j] = j;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Fill the DP table
|
|
27
|
+
for (let i = 1; i <= m; i++) {
|
|
28
|
+
for (let j = 1; j <= n; j++) {
|
|
29
|
+
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
|
30
|
+
dp[i][j] = Math.min(
|
|
31
|
+
dp[i - 1][j] + 1, // Deletion
|
|
32
|
+
dp[i][j - 1] + 1, // Insertion
|
|
33
|
+
dp[i - 1][j - 1] + cost // Substitution
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return dp[m][n];
|
|
39
|
+
}
|