mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-22 03:18:01 +00:00
reenabled @typescript-eslint/no-unnecessary-condition
This commit is contained in:
@@ -97,7 +97,9 @@ rules:
|
|||||||
- allowTemplateLiterals: true
|
- allowTemplateLiterals: true
|
||||||
'@typescript-eslint/no-confusing-void-expression': off
|
'@typescript-eslint/no-confusing-void-expression': off
|
||||||
'@typescript-eslint/no-non-null-assertion': off
|
'@typescript-eslint/no-non-null-assertion': off
|
||||||
'@typescript-eslint/no-unnecessary-condition': off
|
'@typescript-eslint/no-unnecessary-condition':
|
||||||
|
- error
|
||||||
|
- allowConstantLoopConditions: true
|
||||||
'@typescript-eslint/no-untyped-public-signature': off # bugs out on constructors
|
'@typescript-eslint/no-untyped-public-signature': off # bugs out on constructors
|
||||||
'@typescript-eslint/restrict-template-expressions': off
|
'@typescript-eslint/restrict-template-expressions': off
|
||||||
'@typescript-eslint/no-dynamic-delete': off
|
'@typescript-eslint/no-dynamic-delete': off
|
||||||
|
@@ -5,7 +5,7 @@ import { Window, WindowOptions } from './window'
|
|||||||
import { pluginManager } from './pluginManager'
|
import { pluginManager } from './pluginManager'
|
||||||
|
|
||||||
export class Application {
|
export class Application {
|
||||||
private tray: Tray
|
private tray?: Tray
|
||||||
private windows: Window[] = []
|
private windows: Window[] = []
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
@@ -131,10 +131,8 @@ export class Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disableTray (): void {
|
disableTray (): void {
|
||||||
if (this.tray) {
|
this.tray?.destroy()
|
||||||
this.tray.destroy()
|
this.tray = null
|
||||||
this.tray = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasWindows (): boolean {
|
hasWindows (): boolean {
|
||||||
|
@@ -8,17 +8,15 @@ try {
|
|||||||
appPath = path.dirname(require('electron').remote.app.getPath('exe'))
|
appPath = path.dirname(require('electron').remote.app.getPath('exe'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != appPath) {
|
if (fs.existsSync(path.join(appPath, 'terminus-data'))) {
|
||||||
if (fs.existsSync(path.join(appPath, 'terminus-data'))) {
|
fs.renameSync(path.join(appPath, 'terminus-data'), path.join(appPath, 'data'))
|
||||||
fs.renameSync(path.join(appPath, 'terminus-data'), path.join(appPath, 'data'))
|
}
|
||||||
}
|
const portableData = path.join(appPath, 'data')
|
||||||
const portableData = path.join(appPath, 'data')
|
if (fs.existsSync(portableData)) {
|
||||||
if (fs.existsSync(portableData)) {
|
console.log('reset user data to ' + portableData)
|
||||||
console.log('reset user data to ' + portableData)
|
try {
|
||||||
try {
|
require('electron').app.setPath('userData', portableData)
|
||||||
require('electron').app.setPath('userData', portableData)
|
} catch {
|
||||||
} catch {
|
require('electron').remote.app.setPath('userData', portableData)
|
||||||
require('electron').remote.app.setPath('userData', portableData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -35,9 +35,9 @@ export class Window {
|
|||||||
ready: Promise<void>
|
ready: Promise<void>
|
||||||
private visible = new Subject<boolean>()
|
private visible = new Subject<boolean>()
|
||||||
private closed = new Subject<void>()
|
private closed = new Subject<void>()
|
||||||
private window: GlasstronWindow
|
private window?: GlasstronWindow
|
||||||
private windowConfig: ElectronConfig
|
private windowConfig: ElectronConfig
|
||||||
private windowBounds: Rectangle
|
private windowBounds?: Rectangle
|
||||||
private closing = false
|
private closing = false
|
||||||
private lastVibrancy: {enabled: boolean, type?: string} | null = null
|
private lastVibrancy: {enabled: boolean, type?: string} | null = null
|
||||||
private disableVibrancyWhileDragging = false
|
private disableVibrancyWhileDragging = false
|
||||||
|
@@ -8,7 +8,5 @@ export interface HotkeyDescription {
|
|||||||
* must also provide the `hotkeys.foo` config options with the default values
|
* must also provide the `hotkeys.foo` config options with the default values
|
||||||
*/
|
*/
|
||||||
export abstract class HotkeyProvider {
|
export abstract class HotkeyProvider {
|
||||||
hotkeys: HotkeyDescription[] = []
|
|
||||||
|
|
||||||
abstract provide (): Promise<HotkeyDescription[]>
|
abstract provide (): Promise<HotkeyDescription[]>
|
||||||
}
|
}
|
||||||
|
@@ -72,7 +72,7 @@ export class SelectorModalComponent<T> {
|
|||||||
this.modalInstance.dismiss()
|
this.modalInstance.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
iconIsSVG (icon: string): boolean {
|
iconIsSVG (icon?: string): boolean {
|
||||||
return icon?.startsWith('<')
|
return icon?.startsWith('<') ?? false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -161,7 +161,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
_allFocusMode = false
|
_allFocusMode = false
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
private focusedTab: BaseTabComponent
|
private focusedTab: BaseTabComponent|null = null
|
||||||
private maximizedTab: BaseTabComponent|null = null
|
private maximizedTab: BaseTabComponent|null = null
|
||||||
private hotkeysSubscription: Subscription
|
private hotkeysSubscription: Subscription
|
||||||
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
||||||
@@ -211,7 +211,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
||||||
|
|
||||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus || !this.focusedTab) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch (hotkey) {
|
switch (hotkey) {
|
||||||
@@ -280,7 +280,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
return this.root.getAllTabs()
|
return this.root.getAllTabs()
|
||||||
}
|
}
|
||||||
|
|
||||||
getFocusedTab (): BaseTabComponent {
|
getFocusedTab (): BaseTabComponent|null {
|
||||||
return this.focusedTab
|
return this.focusedTab
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,10 +295,8 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
x.emitBlurred()
|
x.emitBlurred()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tab) {
|
tab.emitFocused()
|
||||||
tab.emitFocused()
|
this.focusChanged.next(tab)
|
||||||
this.focusChanged.next(tab)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.maximizedTab !== tab) {
|
if (this.maximizedTab !== tab) {
|
||||||
this.maximizedTab = null
|
this.maximizedTab = null
|
||||||
@@ -314,7 +312,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
/**
|
/**
|
||||||
* Focuses the first available tab inside the given [[SplitContainer]]
|
* Focuses the first available tab inside the given [[SplitContainer]]
|
||||||
*/
|
*/
|
||||||
focusAnyIn (parent: BaseTabComponent | SplitContainer): void {
|
focusAnyIn (parent?: BaseTabComponent | SplitContainer): void {
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -398,6 +396,10 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
* Moves focus in the given direction
|
* Moves focus in the given direction
|
||||||
*/
|
*/
|
||||||
navigate (dir: SplitDirection): void {
|
navigate (dir: SplitDirection): void {
|
||||||
|
if (!this.focusedTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
||||||
let parent = this.getParentOf(rel)
|
let parent = this.getParentOf(rel)
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
@@ -598,7 +600,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider {
|
export class SplitTabRecoveryProvider extends TabRecoveryProvider {
|
||||||
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
||||||
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
if (recoveryToken.type === 'app:split-tab') {
|
||||||
return {
|
return {
|
||||||
type: SplitTabComponent,
|
type: SplitTabComponent,
|
||||||
options: { _recoveredState: recoveryToken },
|
options: { _recoveredState: recoveryToken },
|
||||||
|
@@ -29,7 +29,7 @@ export class StartPageComponent {
|
|||||||
.sort((a: ToolbarButton, b: ToolbarButton) => (a.weight ?? 0) - (b.weight ?? 0))
|
.sort((a: ToolbarButton, b: ToolbarButton) => (a.weight ?? 0) - (b.weight ?? 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizeIcon (icon: string): any {
|
sanitizeIcon (icon?: string): any {
|
||||||
return this.domSanitizer.bypassSecurityTrustHtml(icon ?? '')
|
return this.domSanitizer.bypassSecurityTrustHtml(icon ?? '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,10 +46,10 @@ class CompletionObserver {
|
|||||||
export class AppService {
|
export class AppService {
|
||||||
tabs: BaseTabComponent[] = []
|
tabs: BaseTabComponent[] = []
|
||||||
|
|
||||||
get activeTab (): BaseTabComponent { return this._activeTab }
|
get activeTab (): BaseTabComponent|null { return this._activeTab ?? null }
|
||||||
|
|
||||||
private lastTabIndex = 0
|
private lastTabIndex = 0
|
||||||
private _activeTab: BaseTabComponent
|
private _activeTab?: BaseTabComponent
|
||||||
private closedTabsStack: RecoveryToken[] = []
|
private closedTabsStack: RecoveryToken[] = []
|
||||||
|
|
||||||
private activeTabChange = new Subject<BaseTabComponent>()
|
private activeTabChange = new Subject<BaseTabComponent>()
|
||||||
@@ -190,7 +190,7 @@ export class AppService {
|
|||||||
this._activeTab.emitFocused()
|
this._activeTab.emitFocused()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.tabs.includes(this._activeTab)) {
|
if (this._activeTab && this.tabs.includes(this._activeTab)) {
|
||||||
this.lastTabIndex = this.tabs.indexOf(this._activeTab)
|
this.lastTabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
} else {
|
} else {
|
||||||
this.lastTabIndex = 0
|
this.lastTabIndex = 0
|
||||||
@@ -201,12 +201,10 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
this._activeTab = tab
|
this._activeTab = tab
|
||||||
this.activeTabChange.next(tab)
|
this.activeTabChange.next(tab)
|
||||||
if (this._activeTab) {
|
setImmediate(() => {
|
||||||
setImmediate(() => {
|
this._activeTab?.emitFocused()
|
||||||
this._activeTab.emitFocused()
|
})
|
||||||
})
|
this.hostApp.setTitle(this._activeTab.title)
|
||||||
this.hostApp.setTitle(this._activeTab.title)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getParentTab (tab: BaseTabComponent): SplitTabComponent|null {
|
getParentTab (tab: BaseTabComponent): SplitTabComponent|null {
|
||||||
@@ -229,6 +227,9 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nextTab (): void {
|
nextTab (): void {
|
||||||
|
if (!this._activeTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.tabs.length > 1) {
|
if (this.tabs.length > 1) {
|
||||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
if (tabIndex < this.tabs.length - 1) {
|
if (tabIndex < this.tabs.length - 1) {
|
||||||
@@ -240,6 +241,9 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
previousTab (): void {
|
previousTab (): void {
|
||||||
|
if (!this._activeTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.tabs.length > 1) {
|
if (this.tabs.length > 1) {
|
||||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
if (tabIndex > 0) {
|
if (tabIndex > 0) {
|
||||||
@@ -251,6 +255,9 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
moveSelectedTabLeft (): void {
|
moveSelectedTabLeft (): void {
|
||||||
|
if (!this._activeTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.tabs.length > 1) {
|
if (this.tabs.length > 1) {
|
||||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
if (tabIndex > 0) {
|
if (tabIndex > 0) {
|
||||||
@@ -262,6 +269,9 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
moveSelectedTabRight (): void {
|
moveSelectedTabRight (): void {
|
||||||
|
if (!this._activeTab) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.tabs.length > 1) {
|
if (this.tabs.length > 1) {
|
||||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||||
if (tabIndex < this.tabs.length - 1) {
|
if (tabIndex < this.tabs.length - 1) {
|
||||||
|
@@ -109,10 +109,7 @@ export class ConfigService {
|
|||||||
) {
|
) {
|
||||||
this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
|
this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
|
||||||
this.defaults = configProviders.map(provider => {
|
this.defaults = configProviders.map(provider => {
|
||||||
let defaults = {}
|
let defaults = provider.platformDefaults[hostApp.platform] || {}
|
||||||
if (provider.platformDefaults) {
|
|
||||||
defaults = configMerge(defaults, provider.platformDefaults[hostApp.platform] || {})
|
|
||||||
}
|
|
||||||
if (provider.defaults) {
|
if (provider.defaults) {
|
||||||
defaults = configMerge(defaults, provider.defaults)
|
defaults = configMerge(defaults, provider.defaults)
|
||||||
}
|
}
|
||||||
|
@@ -26,6 +26,7 @@ export class DockingService {
|
|||||||
|
|
||||||
let display = this.electron.screen.getAllDisplays()
|
let display = this.electron.screen.getAllDisplays()
|
||||||
.filter(x => x.id === this.config.store.appearance.dockScreen)[0]
|
.filter(x => x.id === this.config.store.appearance.dockScreen)[0]
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (!display) {
|
if (!display) {
|
||||||
display = this.getCurrentScreen()
|
display = this.getCurrentScreen()
|
||||||
}
|
}
|
||||||
|
@@ -172,7 +172,7 @@ export class HotkeysService {
|
|||||||
return (
|
return (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.config.enabledServices(this.hotkeyProviders)
|
this.config.enabledServices(this.hotkeyProviders)
|
||||||
.map(async x => x.provide ? x.provide() : x.hotkeys)
|
.map(async x => x.provide())
|
||||||
)
|
)
|
||||||
).reduce((a, b) => a.concat(b))
|
).reduce((a, b) => a.concat(b))
|
||||||
}
|
}
|
||||||
@@ -222,7 +222,7 @@ export class HotkeysService {
|
|||||||
if (!(value instanceof Array)) {
|
if (!(value instanceof Array)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (value) {
|
if (value.length > 0) {
|
||||||
value = value.map((item: string | string[]) => typeof item === 'string' ? [item] : item)
|
value = value.map((item: string | string[]) => typeof item === 'string' ? [item] : item)
|
||||||
keys[key] = value
|
keys[key] = value
|
||||||
}
|
}
|
||||||
|
@@ -54,9 +54,7 @@ export class Logger {
|
|||||||
|
|
||||||
private doLog (level: string, ...args: any[]): void {
|
private doLog (level: string, ...args: any[]): void {
|
||||||
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
||||||
if (this.winstonLogger) {
|
this.winstonLogger[level](...args)
|
||||||
this.winstonLogger[level](...args)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ export class TabRecoveryService {
|
|||||||
enabled = false
|
enabled = false
|
||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[]|null,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
log: LogService
|
log: LogService
|
||||||
) {
|
) {
|
||||||
@@ -23,30 +23,23 @@ export class TabRecoveryService {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.localStorage.tabsRecovery = JSON.stringify(
|
window.localStorage.tabsRecovery = JSON.stringify(
|
||||||
await Promise.all(
|
(await Promise.all(
|
||||||
tabs
|
tabs
|
||||||
.map(tab => {
|
.map(async tab => tab.getRecoveryToken().then(r => {
|
||||||
let token = tab.getRecoveryToken()
|
if (r) {
|
||||||
if (token) {
|
r.tabTitle = tab.title
|
||||||
token = token.then(r => {
|
if (tab.color) {
|
||||||
if (r) {
|
r.tabColor = tab.color
|
||||||
r.tabTitle = tab.title
|
}
|
||||||
if (tab.color) {
|
|
||||||
r.tabColor = tab.color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return token
|
return r
|
||||||
})
|
}))
|
||||||
.filter(token => !!token)
|
)).filter(token => !!token)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async recoverTab (token: RecoveryToken): Promise<RecoveredTab|null> {
|
async recoverTab (token: RecoveryToken): Promise<RecoveredTab|null> {
|
||||||
for (const provider of this.config.enabledServices(this.tabRecoveryProviders)) {
|
for (const provider of this.config.enabledServices(this.tabRecoveryProviders ?? [])) {
|
||||||
try {
|
try {
|
||||||
const tab = await provider.recover(token)
|
const tab = await provider.recover(token)
|
||||||
if (tab !== null) {
|
if (tab !== null) {
|
||||||
|
@@ -33,6 +33,7 @@ export class TouchbarService {
|
|||||||
app.tabOpened$.subscribe(tab => {
|
app.tabOpened$.subscribe(tab => {
|
||||||
tab.titleChange$.subscribe(title => {
|
tab.titleChange$.subscribe(title => {
|
||||||
const segment = this.tabSegments[app.tabs.indexOf(tab)]
|
const segment = this.tabSegments[app.tabs.indexOf(tab)]
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (segment) {
|
if (segment) {
|
||||||
segment.label = this.shortenTitle(title)
|
segment.label = this.shortenTitle(title)
|
||||||
this.tabsSegmentedControl.segments = this.tabSegments
|
this.tabsSegmentedControl.segments = this.tabSegments
|
||||||
@@ -41,6 +42,7 @@ export class TouchbarService {
|
|||||||
tab.activity$.subscribe(hasActivity => {
|
tab.activity$.subscribe(hasActivity => {
|
||||||
const showIcon = this.app.activeTab !== tab && hasActivity
|
const showIcon = this.app.activeTab !== tab && hasActivity
|
||||||
const segment = this.tabSegments[app.tabs.indexOf(tab)]
|
const segment = this.tabSegments[app.tabs.indexOf(tab)]
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (segment) {
|
if (segment) {
|
||||||
segment.icon = showIcon ? activityIcon : undefined
|
segment.icon = showIcon ? activityIcon : undefined
|
||||||
}
|
}
|
||||||
@@ -53,7 +55,7 @@ export class TouchbarService {
|
|||||||
label: this.shortenTitle(tab.title),
|
label: this.shortenTitle(tab.title),
|
||||||
}))
|
}))
|
||||||
this.tabsSegmentedControl.segments = this.tabSegments
|
this.tabsSegmentedControl.segments = this.tabSegments
|
||||||
this.tabsSegmentedControl.selectedIndex = this.app.tabs.indexOf(this.app.activeTab)
|
this.tabsSegmentedControl.selectedIndex = this.app.activeTab ? this.app.tabs.indexOf(this.app.activeTab) : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
update (): void {
|
update (): void {
|
||||||
@@ -73,7 +75,7 @@ export class TouchbarService {
|
|||||||
|
|
||||||
this.tabsSegmentedControl = new this.electron.TouchBar.TouchBarSegmentedControl({
|
this.tabsSegmentedControl = new this.electron.TouchBar.TouchBarSegmentedControl({
|
||||||
segments: this.tabSegments,
|
segments: this.tabSegments,
|
||||||
selectedIndex: this.app.tabs.indexOf(this.app.activeTab),
|
selectedIndex: this.app.activeTab ? this.app.tabs.indexOf(this.app.activeTab) : undefined,
|
||||||
change: (selectedIndex) => this.zone.run(() => {
|
change: (selectedIndex) => this.zone.run(() => {
|
||||||
this.app.selectTab(this.app.tabs[selectedIndex])
|
this.app.selectTab(this.app.tabs[selectedIndex])
|
||||||
}),
|
}),
|
||||||
@@ -109,6 +111,7 @@ export class TouchbarService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getCachedNSImage (name: string) {
|
private getCachedNSImage (name: string) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (!this.nsImageCache[name]) {
|
if (!this.nsImageCache[name]) {
|
||||||
this.nsImageCache[name] = this.electron.nativeImage.createFromNamedImage(name, [0, 0, 1])
|
this.nsImageCache[name] = this.electron.nativeImage.createFromNamedImage(name, [0, 0, 1])
|
||||||
}
|
}
|
||||||
|
@@ -113,7 +113,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
|||||||
...items,
|
...items,
|
||||||
{
|
{
|
||||||
label: 'Rename',
|
label: 'Rename',
|
||||||
click: () => this.zone.run(() => tabHeader?.showRenameTabModal()),
|
click: () => this.zone.run(() => tabHeader.showRenameTabModal()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Duplicate',
|
label: 'Duplicate',
|
||||||
|
@@ -16,8 +16,8 @@ import { Subscription } from 'rxjs'
|
|||||||
animations: BaseTerminalTabComponent.animations,
|
animations: BaseTerminalTabComponent.animations,
|
||||||
})
|
})
|
||||||
export class SerialTabComponent extends BaseTerminalTabComponent {
|
export class SerialTabComponent extends BaseTerminalTabComponent {
|
||||||
connection: SerialConnection
|
connection?: SerialConnection
|
||||||
session: SerialSession
|
session?: SerialSession
|
||||||
serialPort: any
|
serialPort: any
|
||||||
private homeEndSubscription: Subscription
|
private homeEndSubscription: Subscription
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ import { SerialTabComponent } from './components/serialTab.component'
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class RecoveryProvider extends TabRecoveryProvider {
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
||||||
if (recoveryToken?.type === 'app:serial-tab') {
|
if (recoveryToken.type === 'app:serial-tab') {
|
||||||
return {
|
return {
|
||||||
type: SerialTabComponent,
|
type: SerialTabComponent,
|
||||||
options: {
|
options: {
|
||||||
|
@@ -10,7 +10,7 @@ import { HotkeyInputModalComponent } from './hotkeyInputModal.component'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class MultiHotkeyInputComponent {
|
export class MultiHotkeyInputComponent {
|
||||||
@Input() model: string[][]
|
@Input() model: string[][] = []
|
||||||
@Output() modelChange = new EventEmitter()
|
@Output() modelChange = new EventEmitter()
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@@ -18,9 +18,6 @@ export class MultiHotkeyInputComponent {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit (): void {
|
ngOnInit (): void {
|
||||||
if (!this.model) {
|
|
||||||
this.model = []
|
|
||||||
}
|
|
||||||
if (typeof this.model === 'string') {
|
if (typeof this.model === 'string') {
|
||||||
this.model = [this.model]
|
this.model = [this.model]
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ export enum SSHAlgorithmType {
|
|||||||
export interface SSHConnection {
|
export interface SSHConnection {
|
||||||
name: string
|
name: string
|
||||||
host: string
|
host: string
|
||||||
port: number
|
port?: number
|
||||||
user: string
|
user: string
|
||||||
auth?: null|'password'|'publicKey'|'agent'|'keyboardInteractive'
|
auth?: null|'password'|'publicKey'|'agent'|'keyboardInteractive'
|
||||||
password?: string
|
password?: string
|
||||||
@@ -112,7 +112,7 @@ export class ForwardedPort {
|
|||||||
|
|
||||||
export class SSHSession extends BaseSession {
|
export class SSHSession extends BaseSession {
|
||||||
scripts?: LoginScript[]
|
scripts?: LoginScript[]
|
||||||
shell: ClientChannel
|
shell?: ClientChannel
|
||||||
ssh: Client
|
ssh: Client
|
||||||
forwardedPorts: ForwardedPort[] = []
|
forwardedPorts: ForwardedPort[] = []
|
||||||
logger: Logger
|
logger: Logger
|
||||||
@@ -282,17 +282,15 @@ export class SSHSession extends BaseSession {
|
|||||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
|
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
|
||||||
return reject()
|
return reject()
|
||||||
}
|
}
|
||||||
if (stream) {
|
const socket = accept()
|
||||||
const socket = accept()
|
stream.pipe(socket)
|
||||||
stream.pipe(socket)
|
socket.pipe(stream)
|
||||||
socket.pipe(stream)
|
stream.on('close', () => {
|
||||||
stream.on('close', () => {
|
socket.destroy()
|
||||||
socket.destroy()
|
})
|
||||||
})
|
socket.on('close', () => {
|
||||||
socket.on('close', () => {
|
stream.close()
|
||||||
stream.close()
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@@ -52,6 +52,7 @@ export class EditConnectionModalComponent {
|
|||||||
this.connection.auth = this.connection.auth ?? null
|
this.connection.auth = this.connection.auth ?? null
|
||||||
|
|
||||||
for (const k of Object.values(SSHAlgorithmType)) {
|
for (const k of Object.values(SSHAlgorithmType)) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (!this.connection.algorithms[k]) {
|
if (!this.connection.algorithms[k]) {
|
||||||
this.connection.algorithms[k] = this.defaultAlgorithms[k]
|
this.connection.algorithms[k] = this.defaultAlgorithms[k]
|
||||||
}
|
}
|
||||||
|
@@ -19,8 +19,8 @@ import { Subscription } from 'rxjs'
|
|||||||
animations: BaseTerminalTabComponent.animations,
|
animations: BaseTerminalTabComponent.animations,
|
||||||
})
|
})
|
||||||
export class SSHTabComponent extends BaseTerminalTabComponent {
|
export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||||
connection: SSHConnection
|
connection?: SSHConnection
|
||||||
session: SSHSession
|
session?: SSHSession
|
||||||
private sessionStack: SSHSession[] = []
|
private sessionStack: SSHSession[] = []
|
||||||
private homeEndSubscription: Subscription
|
private homeEndSubscription: Subscription
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
jumpSession.destroyed$.subscribe(() => session.destroy())
|
jumpSession.destroyed$.subscribe(() => session.destroy())
|
||||||
|
|
||||||
session.jumpStream = await new Promise((resolve, reject) => jumpSession.ssh.forwardOut(
|
session.jumpStream = await new Promise((resolve, reject) => jumpSession.ssh.forwardOut(
|
||||||
'127.0.0.1', 0, session.connection.host, session.connection.port,
|
'127.0.0.1', 0, session.connection.host, session.connection.port ?? 22,
|
||||||
(err, stream) => {
|
(err, stream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
|
jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
|
||||||
@@ -156,7 +156,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
async reconnect (): Promise<void> {
|
async reconnect (): Promise<void> {
|
||||||
this.session?.destroy()
|
this.session?.destroy()
|
||||||
await this.initializeSession()
|
await this.initializeSession()
|
||||||
this.session.releaseInitialDataBuffer()
|
this.session?.releaseInitialDataBuffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
async canClose (): Promise<boolean> {
|
async canClose (): Promise<boolean> {
|
||||||
|
@@ -7,7 +7,7 @@ import { SSHTabComponent } from './components/sshTab.component'
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class RecoveryProvider extends TabRecoveryProvider {
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
||||||
if (recoveryToken?.type === 'app:ssh-tab') {
|
if (recoveryToken.type === 'app:ssh-tab') {
|
||||||
return {
|
return {
|
||||||
type: SSHTabComponent,
|
type: SSHTabComponent,
|
||||||
options: {
|
options: {
|
||||||
|
@@ -35,8 +35,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
]),
|
]),
|
||||||
])]
|
])]
|
||||||
|
|
||||||
session: BaseSession
|
session?: BaseSession
|
||||||
savedState: any
|
savedState?: any
|
||||||
|
|
||||||
@Input() zoom = 0
|
@Input() zoom = 0
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
/** @hidden */
|
/** @hidden */
|
||||||
@HostBinding('class.top-padded') topPadded: boolean
|
@HostBinding('class.top-padded') topPadded: boolean
|
||||||
|
|
||||||
frontend: Frontend
|
frontend?: Frontend
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
frontendIsReady = false
|
frontendIsReady = false
|
||||||
@@ -83,7 +83,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
protected terminalContainersService: TerminalFrontendService
|
protected terminalContainersService: TerminalFrontendService
|
||||||
protected toastr: ToastrServiceProxy
|
protected toastr: ToastrServiceProxy
|
||||||
protected log: LogService
|
protected log: LogService
|
||||||
protected decorators: TerminalDecorator[]
|
protected decorators: TerminalDecorator[] = []
|
||||||
protected contextMenuProviders: TabContextMenuItemProvider[]
|
protected contextMenuProviders: TabContextMenuItemProvider[]
|
||||||
// Deps end
|
// Deps end
|
||||||
|
|
||||||
@@ -95,10 +95,29 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
private termContainerSubscriptions: Subscription[] = []
|
private termContainerSubscriptions: Subscription[] = []
|
||||||
private allFocusModeSubscription: Subscription|null = null
|
private allFocusModeSubscription: Subscription|null = null
|
||||||
|
|
||||||
get input$ (): Observable<Buffer> { return this.frontend.input$ }
|
get input$ (): Observable<Buffer> {
|
||||||
|
if (!this.frontend) {
|
||||||
|
throw new Error('Frontend not ready')
|
||||||
|
}
|
||||||
|
return this.frontend.input$
|
||||||
|
}
|
||||||
|
|
||||||
get output$ (): Observable<string> { return this.output }
|
get output$ (): Observable<string> { return this.output }
|
||||||
get resize$ (): Observable<ResizeEvent> { return this.frontend.resize$ }
|
|
||||||
get alternateScreenActive$ (): Observable<boolean> { return this.frontend.alternateScreenActive$ }
|
get resize$ (): Observable<ResizeEvent> {
|
||||||
|
if (!this.frontend) {
|
||||||
|
throw new Error('Frontend not ready')
|
||||||
|
}
|
||||||
|
return this.frontend.resize$
|
||||||
|
}
|
||||||
|
|
||||||
|
get alternateScreenActive$ (): Observable<boolean> {
|
||||||
|
if (!this.frontend) {
|
||||||
|
throw new Error('Frontend not ready')
|
||||||
|
}
|
||||||
|
return this.frontend.alternateScreenActive$
|
||||||
|
}
|
||||||
|
|
||||||
get frontendReady$ (): Observable<void> { return this.frontendReady }
|
get frontendReady$ (): Observable<void> { return this.frontendReady }
|
||||||
|
|
||||||
constructor (protected injector: Injector) {
|
constructor (protected injector: Injector) {
|
||||||
@@ -119,7 +138,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.contextMenuProviders = injector.get<any>(TabContextMenuItemProvider, null, InjectFlags.Optional) as TabContextMenuItemProvider[]
|
this.contextMenuProviders = injector.get<any>(TabContextMenuItemProvider, null, InjectFlags.Optional) as TabContextMenuItemProvider[]
|
||||||
|
|
||||||
this.logger = this.log.create('baseTerminalTab')
|
this.logger = this.log.create('baseTerminalTab')
|
||||||
this.decorators = this.decorators || []
|
|
||||||
this.setTitle('Terminal')
|
this.setTitle('Terminal')
|
||||||
|
|
||||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
@@ -128,7 +146,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
switch (hotkey) {
|
switch (hotkey) {
|
||||||
case 'ctrl-c':
|
case 'ctrl-c':
|
||||||
if (this.frontend.getSelection()) {
|
if (this.frontend?.getSelection()) {
|
||||||
this.frontend.copySelection()
|
this.frontend.copySelection()
|
||||||
this.frontend.clearSelection()
|
this.frontend.clearSelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
@@ -137,15 +155,15 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'copy':
|
case 'copy':
|
||||||
this.frontend.copySelection()
|
this.frontend?.copySelection()
|
||||||
this.frontend.clearSelection()
|
this.frontend?.clearSelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
break
|
break
|
||||||
case 'paste':
|
case 'paste':
|
||||||
this.paste()
|
this.paste()
|
||||||
break
|
break
|
||||||
case 'clear':
|
case 'clear':
|
||||||
this.frontend.clear()
|
this.frontend?.clear()
|
||||||
break
|
break
|
||||||
case 'zoom-in':
|
case 'zoom-in':
|
||||||
this.zoomIn()
|
this.zoomIn()
|
||||||
@@ -199,9 +217,13 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
ngOnInit (): void {
|
ngOnInit (): void {
|
||||||
|
if (!this.session) {
|
||||||
|
throw new Error('No session set on the tab object by the time ngOnInit is called')
|
||||||
|
}
|
||||||
|
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.configure()
|
this.configure()
|
||||||
this.frontend.focus()
|
this.frontend?.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.frontend = this.terminalContainersService.getFrontend(this.session)
|
this.frontend = this.terminalContainersService.getFrontend(this.session)
|
||||||
@@ -223,10 +245,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.session.resize(columns, rows)
|
this.session?.resize(columns, rows)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
this.session.releaseInitialDataBuffer()
|
this.session?.releaseInitialDataBuffer()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.alternateScreenActive$.subscribe(x => {
|
this.alternateScreenActive$.subscribe(x => {
|
||||||
@@ -242,12 +264,12 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
if (this.hasFocus) {
|
if (this.hasFocus) {
|
||||||
this.frontend.attach(this.content.nativeElement)
|
this.frontend!.attach(this.content.nativeElement)
|
||||||
this.frontend.configure()
|
this.frontend!.configure()
|
||||||
} else {
|
} else {
|
||||||
this.focused$.pipe(first()).subscribe(() => {
|
this.focused$.pipe(first()).subscribe(() => {
|
||||||
this.frontend.attach(this.content.nativeElement)
|
this.frontend!.attach(this.content.nativeElement)
|
||||||
this.frontend.configure()
|
this.frontend!.configure()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -264,7 +286,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
this.frontend.bell$.subscribe(() => {
|
this.frontend.bell$.subscribe(() => {
|
||||||
if (this.config.store.terminal.bell === 'visual') {
|
if (this.config.store.terminal.bell === 'visual') {
|
||||||
this.frontend.visualBell()
|
this.frontend?.visualBell()
|
||||||
}
|
}
|
||||||
if (this.config.store.terminal.bell === 'audible') {
|
if (this.config.store.terminal.bell === 'audible') {
|
||||||
this.bellPlayer.play()
|
this.bellPlayer.play()
|
||||||
@@ -295,9 +317,9 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
if (!(data instanceof Buffer)) {
|
if (!(data instanceof Buffer)) {
|
||||||
data = Buffer.from(data, 'utf-8')
|
data = Buffer.from(data, 'utf-8')
|
||||||
}
|
}
|
||||||
this.session.write(data)
|
this.session?.write(data)
|
||||||
if (this.config.store.terminal.scrollOnInput) {
|
if (this.config.store.terminal.scrollOnInput) {
|
||||||
this.frontend.scrollToBottom()
|
this.frontend?.scrollToBottom()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +327,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
* Feeds input into the terminal frontend
|
* Feeds input into the terminal frontend
|
||||||
*/
|
*/
|
||||||
write (data: string): void {
|
write (data: string): void {
|
||||||
|
if (!this.frontend) {
|
||||||
|
throw new Error('Frontend not ready')
|
||||||
|
}
|
||||||
|
|
||||||
const percentageMatch = /(^|[^\d])(\d+(\.\d+)?)%([^\d]|$)/.exec(data)
|
const percentageMatch = /(^|[^\d])(\d+(\.\d+)?)%([^\d]|$)/.exec(data)
|
||||||
if (!this.alternateScreenActive && percentageMatch) {
|
if (!this.alternateScreenActive && percentageMatch) {
|
||||||
const percentage = percentageMatch[3] ? parseFloat(percentageMatch[2]) : parseInt(percentageMatch[2])
|
const percentage = percentageMatch[3] ? parseFloat(percentageMatch[2]) : parseInt(percentageMatch[2])
|
||||||
@@ -357,7 +383,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
* Applies the user settings to the terminal
|
* Applies the user settings to the terminal
|
||||||
*/
|
*/
|
||||||
configure (): void {
|
configure (): void {
|
||||||
this.frontend.configure()
|
this.frontend?.configure()
|
||||||
|
|
||||||
this.topPadded = this.hostApp.platform === Platform.macOS
|
this.topPadded = this.hostApp.platform === Platform.macOS
|
||||||
&& this.config.store.appearance.frame === 'thin'
|
&& this.config.store.appearance.frame === 'thin'
|
||||||
@@ -374,17 +400,17 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
zoomIn (): void {
|
zoomIn (): void {
|
||||||
this.zoom++
|
this.zoom++
|
||||||
this.frontend.setZoom(this.zoom)
|
this.frontend?.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut (): void {
|
zoomOut (): void {
|
||||||
this.zoom--
|
this.zoom--
|
||||||
this.frontend.setZoom(this.zoom)
|
this.frontend?.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetZoom (): void {
|
resetZoom (): void {
|
||||||
this.zoom = 0
|
this.zoom = 0
|
||||||
this.frontend.setZoom(this.zoom)
|
this.frontend?.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
focusAllPanes (): void {
|
focusAllPanes (): void {
|
||||||
@@ -394,13 +420,13 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
if (this.parent instanceof SplitTabComponent) {
|
if (this.parent instanceof SplitTabComponent) {
|
||||||
this.parent._allFocusMode = true
|
this.parent._allFocusMode = true
|
||||||
this.parent.layout()
|
this.parent.layout()
|
||||||
this.allFocusModeSubscription = this.frontend.input$.subscribe(data => {
|
this.allFocusModeSubscription = this.frontend?.input$.subscribe(data => {
|
||||||
for (const tab of (this.parent as SplitTabComponent).getAllTabs()) {
|
for (const tab of (this.parent as SplitTabComponent).getAllTabs()) {
|
||||||
if (tab !== this && tab instanceof BaseTerminalTabComponent) {
|
if (tab !== this && tab instanceof BaseTerminalTabComponent) {
|
||||||
tab.sendInput(data)
|
tab.sendInput(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}) ?? null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,7 +444,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
ngOnDestroy (): void {
|
ngOnDestroy (): void {
|
||||||
this.frontend.detach(this.content.nativeElement)
|
this.frontend?.detach(this.content.nativeElement)
|
||||||
this.detachTermContainerHandlers()
|
this.detachTermContainerHandlers()
|
||||||
this.config.enabledServices(this.decorators).forEach(decorator => {
|
this.config.enabledServices(this.decorators).forEach(decorator => {
|
||||||
try {
|
try {
|
||||||
@@ -451,6 +477,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
protected attachTermContainerHandlers (): void {
|
protected attachTermContainerHandlers (): void {
|
||||||
this.detachTermContainerHandlers()
|
this.detachTermContainerHandlers()
|
||||||
|
|
||||||
|
if (!this.frontend) {
|
||||||
|
throw new Error('Frontend not ready')
|
||||||
|
}
|
||||||
|
|
||||||
const maybeConfigure = () => {
|
const maybeConfigure = () => {
|
||||||
if (this.hasFocus) {
|
if (this.hasFocus) {
|
||||||
setTimeout(() => this.configure(), 250)
|
setTimeout(() => this.configure(), 250)
|
||||||
@@ -464,8 +494,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
|
||||||
this.focused$.subscribe(() => this.frontend.enableResizing = true),
|
this.focused$.subscribe(() => this.frontend && (this.frontend.enableResizing = true)),
|
||||||
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
|
this.blurred$.subscribe(() => this.frontend && (this.frontend.enableResizing = false)),
|
||||||
|
|
||||||
this.frontend.mouseEvent$.subscribe(async event => {
|
this.frontend.mouseEvent$.subscribe(async event => {
|
||||||
if (event.type === 'mousedown') {
|
if (event.type === 'mousedown') {
|
||||||
@@ -525,6 +555,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected attachSessionHandlers (destroyOnSessionClose = false): void {
|
protected attachSessionHandlers (destroyOnSessionClose = false): void {
|
||||||
|
if (!this.session) {
|
||||||
|
throw new Error('Session not set')
|
||||||
|
}
|
||||||
|
|
||||||
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
||||||
this.session.output$.subscribe(data => {
|
this.session.output$.subscribe(data => {
|
||||||
if (this.enablePassthrough) {
|
if (this.enablePassthrough) {
|
||||||
@@ -537,7 +571,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
if (destroyOnSessionClose) {
|
if (destroyOnSessionClose) {
|
||||||
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
||||||
this.frontend.destroy()
|
this.frontend?.destroy()
|
||||||
this.destroy()
|
this.destroy()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,10 @@ export abstract class TerminalDecorator {
|
|||||||
/**
|
/**
|
||||||
* Automatically cancel @subscription once detached from @terminal
|
* Automatically cancel @subscription once detached from @terminal
|
||||||
*/
|
*/
|
||||||
protected subscribeUntilDetached (terminal: BaseTerminalTabComponent, subscription: Subscription): void {
|
protected subscribeUntilDetached (terminal: BaseTerminalTabComponent, subscription?: Subscription): void {
|
||||||
|
if (!subscription) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!this.smartSubscriptions.has(terminal)) {
|
if (!this.smartSubscriptions.has(terminal)) {
|
||||||
this.smartSubscriptions.set(terminal, [])
|
this.smartSubscriptions.set(terminal, [])
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ export interface ResizeEvent {
|
|||||||
export interface SessionOptions {
|
export interface SessionOptions {
|
||||||
name?: string
|
name?: string
|
||||||
command: string
|
command: string
|
||||||
args: string[]
|
args?: string[]
|
||||||
cwd?: string
|
cwd?: string
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
width?: number
|
width?: number
|
||||||
|
@@ -60,9 +60,7 @@ export class ShellSettingsTabComponent {
|
|||||||
properties: ['openDirectory', 'showHiddenFiles'],
|
properties: ['openDirectory', 'showHiddenFiles'],
|
||||||
}
|
}
|
||||||
)).filePaths
|
)).filePaths
|
||||||
if (paths) {
|
this.config.store.terminal.workingDirectory = paths[0]
|
||||||
this.config.store.terminal.workingDirectory = paths[0]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newProfile (shell: Shell): void {
|
newProfile (shell: Shell): void {
|
||||||
|
@@ -53,7 +53,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
|
|
||||||
initializeSession (columns: number, rows: number): void {
|
initializeSession (columns: number, rows: number): void {
|
||||||
this.sessions.addSession(
|
this.sessions.addSession(
|
||||||
this.session,
|
this.session!,
|
||||||
Object.assign({}, this.sessionOptions, {
|
Object.assign({}, this.sessionOptions, {
|
||||||
width: columns,
|
width: columns,
|
||||||
height: rows,
|
height: rows,
|
||||||
@@ -76,8 +76,8 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCurrentProcess (): Promise<BaseTabProcess|null> {
|
async getCurrentProcess (): Promise<BaseTabProcess|null> {
|
||||||
const children = await this.session.getChildProcesses()
|
const children = await this.session?.getChildProcesses()
|
||||||
if (!children.length) {
|
if (!children?.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -86,8 +86,8 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async canClose (): Promise<boolean> {
|
async canClose (): Promise<boolean> {
|
||||||
const children = await this.session.getChildProcesses()
|
const children = await this.session?.getChildProcesses()
|
||||||
if (children.length === 0) {
|
if (!children?.length) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return (await this.electron.showMessageBox(
|
return (await this.electron.showMessageBox(
|
||||||
@@ -104,6 +104,6 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
ngOnDestroy (): void {
|
ngOnDestroy (): void {
|
||||||
this.homeEndSubscription.unsubscribe()
|
this.homeEndSubscription.unsubscribe()
|
||||||
super.ngOnDestroy()
|
super.ngOnDestroy()
|
||||||
this.session.destroy()
|
this.session?.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ export class DebugDecorator extends TerminalDecorator {
|
|||||||
let sessionOutputBuffer = ''
|
let sessionOutputBuffer = ''
|
||||||
const bufferLength = 8192
|
const bufferLength = 8192
|
||||||
|
|
||||||
this.subscribeUntilDetached(terminal, terminal.session.output$.subscribe(data => {
|
this.subscribeUntilDetached(terminal, terminal.session!.output$.subscribe(data => {
|
||||||
sessionOutputBuffer += data
|
sessionOutputBuffer += data
|
||||||
if (sessionOutputBuffer.length > bufferLength) {
|
if (sessionOutputBuffer.length > bufferLength) {
|
||||||
sessionOutputBuffer = sessionOutputBuffer.substring(sessionOutputBuffer.length - bufferLength)
|
sessionOutputBuffer = sessionOutputBuffer.substring(sessionOutputBuffer.length - bufferLength)
|
||||||
@@ -88,18 +88,18 @@ export class DebugDecorator extends TerminalDecorator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private doSaveState (terminal: TerminalTabComponent) {
|
private doSaveState (terminal: TerminalTabComponent) {
|
||||||
this.saveFile(terminal.frontend.saveState(), 'state.txt')
|
this.saveFile(terminal.frontend!.saveState(), 'state.txt')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doCopyState (terminal: TerminalTabComponent) {
|
private async doCopyState (terminal: TerminalTabComponent) {
|
||||||
const data = '```' + JSON.stringify(terminal.frontend.saveState()) + '```'
|
const data = '```' + JSON.stringify(terminal.frontend!.saveState()) + '```'
|
||||||
this.electron.clipboard.writeText(data)
|
this.electron.clipboard.writeText(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doLoadState (terminal: TerminalTabComponent) {
|
private async doLoadState (terminal: TerminalTabComponent) {
|
||||||
const data = await this.loadFile()
|
const data = await this.loadFile()
|
||||||
if (data) {
|
if (data) {
|
||||||
terminal.frontend.restoreState(data)
|
terminal.frontend!.restoreState(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ export class DebugDecorator extends TerminalDecorator {
|
|||||||
if (data.startsWith('`')) {
|
if (data.startsWith('`')) {
|
||||||
data = data.substring(3, data.length - 3)
|
data = data.substring(3, data.length - 3)
|
||||||
}
|
}
|
||||||
terminal.frontend.restoreState(JSON.parse(data))
|
terminal.frontend!.restoreState(JSON.parse(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ export class DebugDecorator extends TerminalDecorator {
|
|||||||
private async doLoadOutput (terminal: TerminalTabComponent) {
|
private async doLoadOutput (terminal: TerminalTabComponent) {
|
||||||
const data = await this.loadFile()
|
const data = await this.loadFile()
|
||||||
if (data) {
|
if (data) {
|
||||||
terminal.frontend.write(data)
|
terminal.frontend?.write(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export class DebugDecorator extends TerminalDecorator {
|
|||||||
if (data.startsWith('`')) {
|
if (data.startsWith('`')) {
|
||||||
data = data.substring(3, data.length - 3)
|
data = data.substring(3, data.length - 3)
|
||||||
}
|
}
|
||||||
terminal.frontend.write(JSON.parse(data))
|
terminal.frontend?.write(JSON.parse(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,10 +7,10 @@ import { TerminalTabComponent } from '../components/terminalTab.component'
|
|||||||
export class PathDropDecorator extends TerminalDecorator {
|
export class PathDropDecorator extends TerminalDecorator {
|
||||||
attach (terminal: TerminalTabComponent): void {
|
attach (terminal: TerminalTabComponent): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.subscribeUntilDetached(terminal, terminal.frontend.dragOver$.subscribe(event => {
|
this.subscribeUntilDetached(terminal, terminal.frontend?.dragOver$.subscribe(event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}))
|
}))
|
||||||
this.subscribeUntilDetached(terminal, terminal.frontend.drop$.subscribe(event => {
|
this.subscribeUntilDetached(terminal, terminal.frontend?.drop$.subscribe(event => {
|
||||||
for (const file of event.dataTransfer!.files as any) {
|
for (const file of event.dataTransfer!.files as any) {
|
||||||
this.injectPath(terminal, file.path)
|
this.injectPath(terminal, file.path)
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
terminal.write(data)
|
terminal.write(data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sender: data => terminal.session.write(Buffer.from(data)),
|
sender: data => terminal.session!.write(Buffer.from(data)),
|
||||||
on_detect: async detection => {
|
on_detect: async detection => {
|
||||||
try {
|
try {
|
||||||
terminal.enablePassthrough = false
|
terminal.enablePassthrough = false
|
||||||
@@ -50,7 +50,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.subscribeUntilDetached(terminal, terminal.session.binaryOutput$.subscribe(data => {
|
this.subscribeUntilDetached(terminal, terminal.session!.binaryOutput$.subscribe(data => {
|
||||||
const chunkSize = 1024
|
const chunkSize = 1024
|
||||||
for (let i = 0; i <= Math.floor(data.length / chunkSize); i++) {
|
for (let i = 0; i <= Math.floor(data.length / chunkSize); i++) {
|
||||||
try {
|
try {
|
||||||
@@ -153,6 +153,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
this.cancelEvent.toPromise(),
|
this.cancelEvent.toPromise(),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
this.showMessage(terminal, colors.bgRed.black(' Canceled ') + ' ' + details.name)
|
this.showMessage(terminal, colors.bgRed.black(' Canceled ') + ' ' + details.name)
|
||||||
} else {
|
} else {
|
||||||
@@ -207,6 +208,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
|
|
||||||
await xfer.end()
|
await xfer.end()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
this.showMessage(terminal, colors.bgRed.black(' Canceled ') + ' ' + offer.name)
|
this.showMessage(terminal, colors.bgRed.black(' Canceled ') + ' ' + offer.name)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -33,7 +33,7 @@ export class XTermFrontend extends Frontend {
|
|||||||
private search = new SearchAddon()
|
private search = new SearchAddon()
|
||||||
private fitAddon = new FitAddon()
|
private fitAddon = new FitAddon()
|
||||||
private serializeAddon = new SerializeAddon()
|
private serializeAddon = new SerializeAddon()
|
||||||
private ligaturesAddon: LigaturesAddon
|
private ligaturesAddon?: LigaturesAddon
|
||||||
private opened = false
|
private opened = false
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
|
@@ -7,7 +7,7 @@ import { TerminalTabComponent } from './components/terminalTab.component'
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class RecoveryProvider extends TabRecoveryProvider {
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
||||||
if (recoveryToken?.type === 'app:terminal-tab') {
|
if (recoveryToken.type === 'app:terminal-tab') {
|
||||||
return {
|
return {
|
||||||
type: TerminalTabComponent,
|
type: TerminalTabComponent,
|
||||||
options: {
|
options: {
|
||||||
|
@@ -53,16 +53,16 @@ export class TerminalService {
|
|||||||
return slugify(profile.name, { remove: /[:.]/g }).toLowerCase()
|
return slugify(profile.name, { remove: /[:.]/g }).toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProfileByID (id: string): Promise<Profile> {
|
async getProfileByID (id: string): Promise<Profile|null> {
|
||||||
const profiles = await this.getProfiles({ includeHidden: true })
|
const profiles = await this.getProfiles({ includeHidden: true })
|
||||||
return profiles.find(x => this.getProfileID(x) === id) ?? profiles[0]
|
return profiles.find(x => this.getProfileID(x) === id) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches a new terminal with a specific shell and CWD
|
* Launches a new terminal with a specific shell and CWD
|
||||||
* @param pause Wait for a keypress when the shell exits
|
* @param pause Wait for a keypress when the shell exits
|
||||||
*/
|
*/
|
||||||
async openTab (profile?: Profile, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> {
|
async openTab (profile?: Profile|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> {
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
profile = await this.getProfileByID(this.config.store.terminal.profile)
|
profile = await this.getProfileByID(this.config.store.terminal.profile)
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
@@ -101,7 +101,7 @@ export class TerminalService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tab = this.openTabWithOptions(sessionOptions)
|
const tab = this.openTabWithOptions(sessionOptions)
|
||||||
if (profile?.color) {
|
if (profile.color) {
|
||||||
(this.app.getParentTab(tab) ?? tab).color = profile.color
|
(this.app.getParentTab(tab) ?? tab).color = profile.color
|
||||||
}
|
}
|
||||||
return tab
|
return tab
|
||||||
|
@@ -32,7 +32,7 @@ export class UACService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const options = { ...sessionOptions }
|
const options = { ...sessionOptions }
|
||||||
options.args = [options.command, ...options.args]
|
options.args = [options.command, ...options.args ?? []]
|
||||||
options.command = helperPath
|
options.command = helperPath
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
|||||||
const profile = {
|
const profile = {
|
||||||
sessionOptions: {
|
sessionOptions: {
|
||||||
...tab.sessionOptions,
|
...tab.sessionOptions,
|
||||||
cwd: await tab.session.getWorkingDirectory() ?? tab.sessionOptions.cwd,
|
cwd: await tab.session?.getWorkingDirectory() ?? tab.sessionOptions.cwd,
|
||||||
},
|
},
|
||||||
name: tab.sessionOptions.command,
|
name: tab.sessionOptions.command,
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
|||||||
click: () => this.zone.run(async () => {
|
click: () => this.zone.run(async () => {
|
||||||
let workingDirectory = this.config.store.terminal.workingDirectory
|
let workingDirectory = this.config.store.terminal.workingDirectory
|
||||||
if (this.config.store.terminal.alwaysUseWorkingDirectory !== true && tab instanceof TerminalTabComponent) {
|
if (this.config.store.terminal.alwaysUseWorkingDirectory !== true && tab instanceof TerminalTabComponent) {
|
||||||
workingDirectory = await tab.session.getWorkingDirectory()
|
workingDirectory = await tab.session?.getWorkingDirectory()
|
||||||
}
|
}
|
||||||
await this.terminalService.openTab(profile, workingDirectory)
|
await this.terminalService.openTab(profile, workingDirectory)
|
||||||
}),
|
}),
|
||||||
@@ -150,7 +150,7 @@ export class CopyPasteContextMenu extends TabContextMenuItemProvider {
|
|||||||
click: (): void => {
|
click: (): void => {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tab.frontend.copySelection()
|
tab.frontend?.copySelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -174,7 +174,7 @@ export class LegacyContextMenu extends TabContextMenuItemProvider {
|
|||||||
weight = 1
|
weight = 1
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@Optional() @Inject(TerminalContextMenuItemProvider) protected contextMenuProviders: TerminalContextMenuItemProvider[],
|
@Optional() @Inject(TerminalContextMenuItemProvider) protected contextMenuProviders: TerminalContextMenuItemProvider[]|null,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user