tabby-tabbyspaces 0.0.1
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/.claude/settings.local.json +15 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- package/CHANGELOG.md +20 -0
- package/CLAUDE.md +159 -0
- package/CONTRIBUTING.md +64 -0
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/RELEASE_PLAN.md +161 -0
- package/dist/build-config.d.ts +4 -0
- package/dist/build-config.d.ts.map +1 -0
- package/dist/components/paneEditor.component.d.ts +14 -0
- package/dist/components/paneEditor.component.d.ts.map +1 -0
- package/dist/components/splitPreview.component.d.ts +36 -0
- package/dist/components/splitPreview.component.d.ts.map +1 -0
- package/dist/components/workspaceEditor.component.d.ts +29 -0
- package/dist/components/workspaceEditor.component.d.ts.map +1 -0
- package/dist/components/workspaceList.component.d.ts +28 -0
- package/dist/components/workspaceList.component.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.LICENSE.txt +43 -0
- package/dist/index.js.map +1 -0
- package/dist/models/workspace.model.d.ts +77 -0
- package/dist/models/workspace.model.d.ts.map +1 -0
- package/dist/providers/config.provider.d.ts +9 -0
- package/dist/providers/config.provider.d.ts.map +1 -0
- package/dist/providers/settings.provider.d.ts +8 -0
- package/dist/providers/settings.provider.d.ts.map +1 -0
- package/dist/providers/toolbar.provider.d.ts +13 -0
- package/dist/providers/toolbar.provider.d.ts.map +1 -0
- package/dist/services/workspaceEditor.service.d.ts +25 -0
- package/dist/services/workspaceEditor.service.d.ts.map +1 -0
- package/package.json +73 -0
- package/screenshots/editor.png +0 -0
- package/screenshots/pane-edit.png +0 -0
- package/screenshots/workspace-edit.png +0 -0
- package/scripts/build-dev.js +46 -0
- package/src/build-config.ts +8 -0
- package/src/components/paneEditor.component.pug +46 -0
- package/src/components/paneEditor.component.scss +112 -0
- package/src/components/paneEditor.component.ts +33 -0
- package/src/components/splitPreview.component.pug +45 -0
- package/src/components/splitPreview.component.scss +126 -0
- package/src/components/splitPreview.component.ts +111 -0
- package/src/components/workspaceEditor.component.pug +84 -0
- package/src/components/workspaceEditor.component.scss +169 -0
- package/src/components/workspaceEditor.component.ts +181 -0
- package/src/components/workspaceList.component.pug +46 -0
- package/src/components/workspaceList.component.scss +112 -0
- package/src/components/workspaceList.component.ts +124 -0
- package/src/index.ts +38 -0
- package/src/models/workspace.model.ts +126 -0
- package/src/providers/config.provider.ts +12 -0
- package/src/providers/settings.provider.ts +15 -0
- package/src/providers/toolbar.provider.ts +81 -0
- package/src/services/workspaceEditor.service.ts +228 -0
- package/tsconfig.json +29 -0
- package/webpack.config.js +62 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core'
|
|
2
|
+
import { CommonModule } from '@angular/common'
|
|
3
|
+
import { FormsModule } from '@angular/forms'
|
|
4
|
+
import { ConfigProvider, ToolbarButtonProvider } from 'tabby-core'
|
|
5
|
+
import { SettingsTabProvider } from 'tabby-settings'
|
|
6
|
+
|
|
7
|
+
import { WorkspaceEditorConfigProvider } from './providers/config.provider'
|
|
8
|
+
import { WorkspaceEditorSettingsProvider } from './providers/settings.provider'
|
|
9
|
+
import { WorkspaceToolbarProvider } from './providers/toolbar.provider'
|
|
10
|
+
import { WorkspaceEditorService } from './services/workspaceEditor.service'
|
|
11
|
+
|
|
12
|
+
import { WorkspaceListComponent } from './components/workspaceList.component'
|
|
13
|
+
import { WorkspaceEditorComponent } from './components/workspaceEditor.component'
|
|
14
|
+
import { PaneEditorComponent } from './components/paneEditor.component'
|
|
15
|
+
import { SplitPreviewComponent } from './components/splitPreview.component'
|
|
16
|
+
|
|
17
|
+
@NgModule({
|
|
18
|
+
imports: [CommonModule, FormsModule],
|
|
19
|
+
providers: [
|
|
20
|
+
{ provide: ConfigProvider, useClass: WorkspaceEditorConfigProvider, multi: true },
|
|
21
|
+
{ provide: SettingsTabProvider, useClass: WorkspaceEditorSettingsProvider, multi: true },
|
|
22
|
+
{ provide: ToolbarButtonProvider, useClass: WorkspaceToolbarProvider, multi: true },
|
|
23
|
+
WorkspaceEditorService,
|
|
24
|
+
],
|
|
25
|
+
declarations: [
|
|
26
|
+
WorkspaceListComponent,
|
|
27
|
+
WorkspaceEditorComponent,
|
|
28
|
+
PaneEditorComponent,
|
|
29
|
+
SplitPreviewComponent,
|
|
30
|
+
],
|
|
31
|
+
})
|
|
32
|
+
export default class WorkspaceEditorModule {}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
WorkspaceEditorService,
|
|
36
|
+
WorkspaceEditorConfigProvider,
|
|
37
|
+
WorkspaceEditorSettingsProvider,
|
|
38
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Tabby profile interfaces
|
|
2
|
+
export interface TabbyProfileOptions {
|
|
3
|
+
command?: string
|
|
4
|
+
args?: string[]
|
|
5
|
+
cwd?: string
|
|
6
|
+
env?: Record<string, string>
|
|
7
|
+
restoreFromPTYID?: boolean
|
|
8
|
+
width?: number | null
|
|
9
|
+
height?: number | null
|
|
10
|
+
pauseAfterExit?: boolean
|
|
11
|
+
runAsAdministrator?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TabbyProfile {
|
|
15
|
+
id: string
|
|
16
|
+
type: string
|
|
17
|
+
name: string
|
|
18
|
+
group?: string
|
|
19
|
+
icon?: string
|
|
20
|
+
color?: string
|
|
21
|
+
options?: TabbyProfileOptions
|
|
22
|
+
isBuiltin?: boolean
|
|
23
|
+
isTemplate?: boolean
|
|
24
|
+
weight?: number
|
|
25
|
+
disableDynamicTitle?: boolean
|
|
26
|
+
terminalColorScheme?: string | null
|
|
27
|
+
behaviorOnSessionEnd?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TabbyRecoveryToken {
|
|
31
|
+
type: string
|
|
32
|
+
orientation?: 'h' | 'v'
|
|
33
|
+
ratios?: number[]
|
|
34
|
+
children?: TabbyRecoveryToken[]
|
|
35
|
+
profile?: Partial<TabbyProfile>
|
|
36
|
+
savedState?: boolean
|
|
37
|
+
tabTitle?: string
|
|
38
|
+
tabCustomTitle?: string
|
|
39
|
+
disableDynamicTitle?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TabbySplitLayoutProfile {
|
|
43
|
+
id: string
|
|
44
|
+
type: 'split-layout'
|
|
45
|
+
name: string
|
|
46
|
+
group: string
|
|
47
|
+
icon?: string
|
|
48
|
+
color?: string
|
|
49
|
+
isBuiltin: boolean
|
|
50
|
+
options: {
|
|
51
|
+
recoveryToken: TabbyRecoveryToken
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Workspace interfaces
|
|
56
|
+
export interface WorkspacePane {
|
|
57
|
+
id: string
|
|
58
|
+
profileId: string
|
|
59
|
+
cwd?: string
|
|
60
|
+
startupCommand?: string
|
|
61
|
+
title?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface WorkspaceSplit {
|
|
65
|
+
orientation: 'horizontal' | 'vertical'
|
|
66
|
+
ratios: number[]
|
|
67
|
+
children: (WorkspacePane | WorkspaceSplit)[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface Workspace {
|
|
71
|
+
id: string
|
|
72
|
+
name: string
|
|
73
|
+
icon?: string
|
|
74
|
+
color?: string
|
|
75
|
+
root: WorkspaceSplit
|
|
76
|
+
isDefault?: boolean
|
|
77
|
+
hotkey?: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isWorkspaceSplit(node: WorkspacePane | WorkspaceSplit): node is WorkspaceSplit {
|
|
81
|
+
return 'orientation' in node && 'children' in node
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createDefaultPane(): WorkspacePane {
|
|
85
|
+
return {
|
|
86
|
+
id: generateUUID(),
|
|
87
|
+
profileId: '',
|
|
88
|
+
cwd: '',
|
|
89
|
+
startupCommand: '',
|
|
90
|
+
title: '',
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createDefaultSplit(orientation: 'horizontal' | 'vertical' = 'horizontal'): WorkspaceSplit {
|
|
95
|
+
return {
|
|
96
|
+
orientation,
|
|
97
|
+
ratios: [0.5, 0.5],
|
|
98
|
+
children: [createDefaultPane(), createDefaultPane()],
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createDefaultWorkspace(name: string = 'New Workspace'): Workspace {
|
|
103
|
+
return {
|
|
104
|
+
id: generateUUID(),
|
|
105
|
+
name,
|
|
106
|
+
icon: 'columns',
|
|
107
|
+
color: '#3b82f6',
|
|
108
|
+
root: createDefaultSplit(),
|
|
109
|
+
isDefault: false,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function generateUUID(): string {
|
|
114
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
115
|
+
const r = (Math.random() * 16) | 0
|
|
116
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
|
117
|
+
return v.toString(16)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function countPanes(node: WorkspacePane | WorkspaceSplit): number {
|
|
122
|
+
if (isWorkspaceSplit(node)) {
|
|
123
|
+
return node.children.reduce((sum, child) => sum + countPanes(child), 0)
|
|
124
|
+
}
|
|
125
|
+
return 1
|
|
126
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core'
|
|
2
|
+
import { ConfigProvider } from 'tabby-core'
|
|
3
|
+
import { CONFIG_KEY } from '../build-config'
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class WorkspaceEditorConfigProvider extends ConfigProvider {
|
|
7
|
+
defaults = {
|
|
8
|
+
[CONFIG_KEY]: {
|
|
9
|
+
workspaces: [],
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core'
|
|
2
|
+
import { SettingsTabProvider } from 'tabby-settings'
|
|
3
|
+
import { WorkspaceListComponent } from '../components/workspaceList.component'
|
|
4
|
+
import { CONFIG_KEY, DISPLAY_NAME } from '../build-config'
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class WorkspaceEditorSettingsProvider extends SettingsTabProvider {
|
|
8
|
+
id = CONFIG_KEY
|
|
9
|
+
icon = 'columns'
|
|
10
|
+
title = DISPLAY_NAME
|
|
11
|
+
|
|
12
|
+
getComponentType(): any {
|
|
13
|
+
return WorkspaceListComponent
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core'
|
|
2
|
+
import { ToolbarButtonProvider, ToolbarButton, ProfilesService, AppService } from 'tabby-core'
|
|
3
|
+
import { WorkspaceEditorService } from '../services/workspaceEditor.service'
|
|
4
|
+
import { SettingsTabComponent } from 'tabby-settings'
|
|
5
|
+
import { CONFIG_KEY, DISPLAY_NAME } from '../build-config'
|
|
6
|
+
import { countPanes } from '../models/workspace.model'
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class WorkspaceToolbarProvider extends ToolbarButtonProvider {
|
|
10
|
+
constructor(
|
|
11
|
+
private workspaceService: WorkspaceEditorService,
|
|
12
|
+
private profilesService: ProfilesService,
|
|
13
|
+
private app: AppService
|
|
14
|
+
) {
|
|
15
|
+
super()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
provide(): ToolbarButton[] {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
22
|
+
<rect x="3" y="3" width="7" height="7"/>
|
|
23
|
+
<rect x="14" y="3" width="7" height="7"/>
|
|
24
|
+
<rect x="14" y="14" width="7" height="7"/>
|
|
25
|
+
<rect x="3" y="14" width="7" height="7"/>
|
|
26
|
+
</svg>`,
|
|
27
|
+
title: DISPLAY_NAME,
|
|
28
|
+
weight: 5,
|
|
29
|
+
click: () => this.showWorkspaceSelector()
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private async showWorkspaceSelector(): Promise<void> {
|
|
35
|
+
const workspaces = this.workspaceService.getWorkspaces()
|
|
36
|
+
|
|
37
|
+
if (workspaces.length === 0) {
|
|
38
|
+
this.openSettings()
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const options = workspaces.map((ws) => ({
|
|
43
|
+
name: ws.name,
|
|
44
|
+
description: `${countPanes(ws.root)} panes`,
|
|
45
|
+
icon: ws.icon || 'grid',
|
|
46
|
+
color: ws.color,
|
|
47
|
+
result: ws.id
|
|
48
|
+
}))
|
|
49
|
+
|
|
50
|
+
// Add option to open settings
|
|
51
|
+
options.push({
|
|
52
|
+
name: 'Manage Workspaces...',
|
|
53
|
+
description: 'Create and edit workspaces',
|
|
54
|
+
icon: 'cog',
|
|
55
|
+
color: undefined,
|
|
56
|
+
result: '__settings__'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const selectedId = await this.app.showSelector('Select Workspace', options)
|
|
60
|
+
|
|
61
|
+
if (selectedId === '__settings__') {
|
|
62
|
+
this.openSettings()
|
|
63
|
+
} else if (selectedId) {
|
|
64
|
+
this.openWorkspace(selectedId)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private openSettings(): void {
|
|
69
|
+
this.app.openNewTabRaw({ type: SettingsTabComponent, inputs: { activeTab: CONFIG_KEY } })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private openWorkspace(workspaceId: string): void {
|
|
73
|
+
const workspaces = this.workspaceService.getWorkspaces()
|
|
74
|
+
const workspace = workspaces.find((w) => w.id === workspaceId)
|
|
75
|
+
|
|
76
|
+
if (!workspace) return
|
|
77
|
+
|
|
78
|
+
const profile = this.workspaceService.generateTabbyProfile(workspace)
|
|
79
|
+
this.profilesService.openNewTabForProfile(profile)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core'
|
|
2
|
+
import { ConfigService, NotificationsService, ProfilesService } from 'tabby-core'
|
|
3
|
+
import {
|
|
4
|
+
Workspace,
|
|
5
|
+
WorkspacePane,
|
|
6
|
+
WorkspaceSplit,
|
|
7
|
+
isWorkspaceSplit,
|
|
8
|
+
generateUUID,
|
|
9
|
+
TabbyProfile,
|
|
10
|
+
TabbyRecoveryToken,
|
|
11
|
+
TabbySplitLayoutProfile,
|
|
12
|
+
} from '../models/workspace.model'
|
|
13
|
+
import { CONFIG_KEY, DISPLAY_NAME } from '../build-config'
|
|
14
|
+
|
|
15
|
+
@Injectable({ providedIn: 'root' })
|
|
16
|
+
export class WorkspaceEditorService {
|
|
17
|
+
constructor(
|
|
18
|
+
private config: ConfigService,
|
|
19
|
+
private notifications: NotificationsService,
|
|
20
|
+
private profilesService: ProfilesService
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
getWorkspaces(): Workspace[] {
|
|
24
|
+
return this.config.store[CONFIG_KEY]?.workspaces ?? []
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async saveWorkspaces(workspaces: Workspace[]): Promise<boolean> {
|
|
28
|
+
this.config.store[CONFIG_KEY].workspaces = workspaces
|
|
29
|
+
this.syncTabbyProfiles(workspaces)
|
|
30
|
+
return await this.saveConfig()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async addWorkspace(workspace: Workspace): Promise<void> {
|
|
34
|
+
const workspaces = this.getWorkspaces()
|
|
35
|
+
workspaces.push(workspace)
|
|
36
|
+
await this.saveWorkspaces(workspaces)
|
|
37
|
+
this.notifications.info(`Workspace "${workspace.name}" created`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async updateWorkspace(workspace: Workspace): Promise<void> {
|
|
41
|
+
const workspaces = this.getWorkspaces()
|
|
42
|
+
const index = workspaces.findIndex((w) => w.id === workspace.id)
|
|
43
|
+
if (index !== -1) {
|
|
44
|
+
workspaces[index] = workspace
|
|
45
|
+
await this.saveWorkspaces(workspaces)
|
|
46
|
+
this.notifications.info(`Workspace "${workspace.name}" updated`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async deleteWorkspace(workspaceId: string): Promise<void> {
|
|
51
|
+
const workspaces = this.getWorkspaces()
|
|
52
|
+
const workspace = workspaces.find((w) => w.id === workspaceId)
|
|
53
|
+
const filtered = workspaces.filter((w) => w.id !== workspaceId)
|
|
54
|
+
await this.saveWorkspaces(filtered)
|
|
55
|
+
if (workspace) {
|
|
56
|
+
this.notifications.info(`Workspace "${workspace.name}" deleted`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async getAvailableProfiles(): Promise<TabbyProfile[]> {
|
|
61
|
+
const allProfiles = await this.profilesService.getProfiles()
|
|
62
|
+
return allProfiles.filter(
|
|
63
|
+
(p) => p.type === 'local' && !p.id?.startsWith('split-layout:')
|
|
64
|
+
) as TabbyProfile[]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private syncTabbyProfiles(workspaces: Workspace[]): void {
|
|
68
|
+
const profiles: (TabbyProfile | TabbySplitLayoutProfile)[] = this.config.store.profiles ?? []
|
|
69
|
+
|
|
70
|
+
// Remove old plugin profiles (mutate in place)
|
|
71
|
+
for (let i = profiles.length - 1; i >= 0; i--) {
|
|
72
|
+
if (profiles[i].id?.startsWith(`split-layout:${CONFIG_KEY}:`)) {
|
|
73
|
+
profiles.splice(i, 1)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add new workspace profiles
|
|
78
|
+
for (const workspace of workspaces) {
|
|
79
|
+
const tabbyProfile = this.generateTabbyProfile(workspace)
|
|
80
|
+
profiles.push(tabbyProfile)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
generateTabbyProfile(workspace: Workspace): TabbySplitLayoutProfile {
|
|
85
|
+
const safeName = this.sanitizeForProfileId(workspace.name)
|
|
86
|
+
return {
|
|
87
|
+
id: `split-layout:${CONFIG_KEY}:${safeName}:${workspace.id}`,
|
|
88
|
+
type: 'split-layout',
|
|
89
|
+
name: workspace.name,
|
|
90
|
+
group: DISPLAY_NAME,
|
|
91
|
+
icon: workspace.icon,
|
|
92
|
+
color: workspace.color,
|
|
93
|
+
isBuiltin: false,
|
|
94
|
+
options: {
|
|
95
|
+
recoveryToken: this.generateRecoveryToken(workspace.root),
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private generateRecoveryToken(split: WorkspaceSplit): TabbyRecoveryToken {
|
|
101
|
+
return {
|
|
102
|
+
type: 'app:split-tab',
|
|
103
|
+
orientation: split.orientation === 'horizontal' ? 'h' : 'v',
|
|
104
|
+
ratios: split.ratios,
|
|
105
|
+
children: split.children.map((child) => {
|
|
106
|
+
if (isWorkspaceSplit(child)) {
|
|
107
|
+
return this.generateRecoveryToken(child)
|
|
108
|
+
}
|
|
109
|
+
return this.generatePaneToken(child)
|
|
110
|
+
}),
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private generatePaneToken(pane: WorkspacePane): TabbyRecoveryToken {
|
|
115
|
+
const baseProfile = this.getProfileById(pane.profileId)
|
|
116
|
+
|
|
117
|
+
if (!baseProfile) {
|
|
118
|
+
return {
|
|
119
|
+
type: 'app:local-tab',
|
|
120
|
+
profile: {
|
|
121
|
+
type: 'local',
|
|
122
|
+
name: 'Shell',
|
|
123
|
+
},
|
|
124
|
+
savedState: false,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build complete profile object like Tabby expects
|
|
129
|
+
const options = {
|
|
130
|
+
restoreFromPTYID: false,
|
|
131
|
+
command: baseProfile.options?.command || '',
|
|
132
|
+
args: baseProfile.options?.args || [],
|
|
133
|
+
cwd: pane.cwd || baseProfile.options?.cwd || '',
|
|
134
|
+
env: baseProfile.options?.env || {},
|
|
135
|
+
width: null,
|
|
136
|
+
height: null,
|
|
137
|
+
pauseAfterExit: false,
|
|
138
|
+
runAsAdministrator: false,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Handle startup command for different shells
|
|
142
|
+
if (pane.startupCommand) {
|
|
143
|
+
const cmd = baseProfile.options?.command || ''
|
|
144
|
+
if (cmd.includes('nu.exe') || baseProfile.name?.toLowerCase().includes('nushell')) {
|
|
145
|
+
options.args = ['-e', pane.startupCommand]
|
|
146
|
+
} else if (cmd.includes('powershell') || cmd.includes('pwsh')) {
|
|
147
|
+
options.args = ['-NoExit', '-Command', pane.startupCommand]
|
|
148
|
+
} else if (cmd.includes('cmd.exe')) {
|
|
149
|
+
options.args = ['/K', pane.startupCommand]
|
|
150
|
+
} else {
|
|
151
|
+
options.args = ['-c', pane.startupCommand]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const profile = {
|
|
156
|
+
id: baseProfile.id,
|
|
157
|
+
type: 'local',
|
|
158
|
+
name: baseProfile.name || 'Shell',
|
|
159
|
+
group: baseProfile.group || '',
|
|
160
|
+
options,
|
|
161
|
+
icon: baseProfile.icon || '',
|
|
162
|
+
color: baseProfile.color || '',
|
|
163
|
+
disableDynamicTitle: false,
|
|
164
|
+
weight: 0,
|
|
165
|
+
isBuiltin: false,
|
|
166
|
+
isTemplate: false,
|
|
167
|
+
terminalColorScheme: null,
|
|
168
|
+
behaviorOnSessionEnd: 'auto',
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
type: 'app:local-tab',
|
|
173
|
+
profile,
|
|
174
|
+
savedState: false,
|
|
175
|
+
tabTitle: pane.title || '',
|
|
176
|
+
tabCustomTitle: pane.title || '',
|
|
177
|
+
disableDynamicTitle: !!pane.title,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
duplicateWorkspace(workspace: Workspace): Workspace {
|
|
182
|
+
const clone = JSON.parse(JSON.stringify(workspace)) as Workspace
|
|
183
|
+
clone.id = generateUUID()
|
|
184
|
+
clone.name = `${workspace.name} (Copy)`
|
|
185
|
+
clone.isDefault = false
|
|
186
|
+
this.regenerateIds(clone.root)
|
|
187
|
+
return clone
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private regenerateIds(node: WorkspacePane | WorkspaceSplit): void {
|
|
191
|
+
if (isWorkspaceSplit(node)) {
|
|
192
|
+
for (const child of node.children) {
|
|
193
|
+
this.regenerateIds(child)
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
node.id = generateUUID()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private sanitizeForProfileId(name: string): string {
|
|
201
|
+
return name
|
|
202
|
+
.toLowerCase()
|
|
203
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
204
|
+
.replace(/-+/g, '-')
|
|
205
|
+
.replace(/^-|-$/g, '')
|
|
206
|
+
|| 'workspace'
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private getProfileById(profileId: string): TabbyProfile | undefined {
|
|
210
|
+
const profiles: TabbyProfile[] = this.config.store.profiles ?? []
|
|
211
|
+
return profiles.find((p) => p.id === profileId && p.type === 'local')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
getProfileName(profileId: string): string | undefined {
|
|
215
|
+
return this.getProfileById(profileId)?.name
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async saveConfig(): Promise<boolean> {
|
|
219
|
+
try {
|
|
220
|
+
await this.config.save()
|
|
221
|
+
return true
|
|
222
|
+
} catch (error) {
|
|
223
|
+
this.notifications.error('Failed to save configuration')
|
|
224
|
+
console.error('TabbySpaces save error:', error)
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"lib": ["ES2020", "DOM"],
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"strict": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"experimentalDecorators": true,
|
|
17
|
+
"emitDecoratorMetadata": true,
|
|
18
|
+
"resolveJsonModule": true,
|
|
19
|
+
"baseUrl": ".",
|
|
20
|
+
"paths": {
|
|
21
|
+
"tabby-core": ["node_modules/tabby-core"],
|
|
22
|
+
"tabby-settings": ["node_modules/tabby-settings"],
|
|
23
|
+
"tabby-local": ["node_modules/tabby-local"],
|
|
24
|
+
"tabby-terminal": ["node_modules/tabby-terminal"]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"include": ["src/**/*"],
|
|
28
|
+
"exclude": ["node_modules", "dist"]
|
|
29
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const webpack = require('webpack')
|
|
3
|
+
|
|
4
|
+
module.exports = (env = {}) => ({
|
|
5
|
+
target: 'node',
|
|
6
|
+
entry: './src/index.ts',
|
|
7
|
+
context: __dirname,
|
|
8
|
+
devtool: 'source-map',
|
|
9
|
+
output: {
|
|
10
|
+
path: path.resolve(__dirname, 'dist'),
|
|
11
|
+
filename: 'index.js',
|
|
12
|
+
pathinfo: true,
|
|
13
|
+
libraryTarget: 'umd',
|
|
14
|
+
publicPath: 'auto',
|
|
15
|
+
},
|
|
16
|
+
resolve: {
|
|
17
|
+
modules: ['.', 'src', 'node_modules'],
|
|
18
|
+
extensions: ['.ts', '.js'],
|
|
19
|
+
},
|
|
20
|
+
module: {
|
|
21
|
+
rules: [
|
|
22
|
+
{
|
|
23
|
+
test: /\.ts$/,
|
|
24
|
+
use: {
|
|
25
|
+
loader: 'ts-loader',
|
|
26
|
+
options: {
|
|
27
|
+
configFile: path.resolve(__dirname, 'tsconfig.json'),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
test: /\.pug$/,
|
|
33
|
+
use: ['apply-loader', 'pug-loader'],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
test: /\.scss$/,
|
|
37
|
+
use: ['to-string-loader', 'css-loader', 'sass-loader'],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
test: /\.css$/,
|
|
41
|
+
use: ['to-string-loader', 'css-loader'],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
externals: [
|
|
46
|
+
'fs',
|
|
47
|
+
'path',
|
|
48
|
+
'electron',
|
|
49
|
+
/^rxjs/,
|
|
50
|
+
/^@angular/,
|
|
51
|
+
/^@ng-bootstrap/,
|
|
52
|
+
/^tabby-/,
|
|
53
|
+
/^zone\.js/,
|
|
54
|
+
],
|
|
55
|
+
plugins: [
|
|
56
|
+
new webpack.DefinePlugin({
|
|
57
|
+
__CONFIG_KEY__: JSON.stringify(env.dev ? 'tabbyspaces_dev' : 'tabbyspaces'),
|
|
58
|
+
__DISPLAY_NAME__: JSON.stringify(env.dev ? 'TabbySpaces DEV' : 'TabbySpaces'),
|
|
59
|
+
__IS_DEV__: env.dev ? 'true' : 'false',
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
})
|