started separating terminus-electron and terminus-web

This commit is contained in:
Eugene Pankov
2021-05-24 17:48:12 +02:00
parent c19e131d8c
commit 012986dc7e
94 changed files with 1899 additions and 972 deletions

View File

@@ -1,10 +1,9 @@
import type { MenuItemConstructorOptions } from 'electron'
import { Observable, Subject, Subscription } from 'rxjs'
import { first } from 'rxjs/operators'
import colors from 'ansi-colors'
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer } from 'terminus-core'
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService } from 'terminus-core'
import { BaseSession } from '../session'
import { TerminalFrontendService } from '../services/terminalFrontend.service'
@@ -84,6 +83,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
protected hostApp: HostAppService
protected hotkeys: HotkeysService
protected electron: ElectronService
protected platform: PlatformService
protected terminalContainersService: TerminalFrontendService
protected notifications: NotificationsService
protected log: LogService
@@ -136,6 +136,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.hostApp = injector.get(HostAppService)
this.hotkeys = injector.get(HotkeysService)
this.electron = injector.get(ElectronService)
this.platform = injector.get(PlatformService)
this.terminalContainersService = injector.get(TerminalFrontendService)
this.notifications = injector.get(NotificationsService)
this.log = injector.get(LogService)
@@ -312,8 +313,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
}
}
async buildContextMenu (): Promise<MenuItemConstructorOptions[]> {
let items: MenuItemConstructorOptions[] = []
async buildContextMenu (): Promise<MenuItemOptions[]> {
let items: MenuItemOptions[] = []
for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this)))) {
items = items.concat(section)
items.push({ type: 'separator' })
@@ -498,6 +499,16 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.termContainerSubscriptions.cancelAll()
}
protected async handleRightClick (event: MouseEvent): Promise<void> {
event.preventDefault()
event.stopPropagation()
if (this.config.store.terminal.rightClick === 'menu') {
this.platform.popupContextMenu(await this.buildContextMenu(), event)
} else if (this.config.store.terminal.rightClick === 'paste') {
this.paste()
}
}
protected attachTermContainerHandlers (): void {
this.detachTermContainerHandlers()
@@ -531,13 +542,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
return
}
if (event.which === 3 || event.which === 1 && event.ctrlKey) {
if (this.config.store.terminal.rightClick === 'menu') {
this.hostApp.popupContextMenu(await this.buildContextMenu())
} else if (this.config.store.terminal.rightClick === 'paste') {
this.paste()
}
event.preventDefault()
event.stopPropagation()
this.handleRightClick(event)
return
}
}

View File

@@ -1,4 +1,4 @@
import type { MenuItemConstructorOptions } from 'electron'
import type { MenuItemOptions } from 'terminus-core'
import { BaseTerminalTabComponent } from './baseTerminalTab.component'
/**
@@ -8,5 +8,5 @@ import { BaseTerminalTabComponent } from './baseTerminalTab.component'
export abstract class TerminalContextMenuItemProvider {
weight: number
abstract getItems (tab: BaseTerminalTabComponent): Promise<MenuItemConstructorOptions[]>
abstract getItems (tab: BaseTerminalTabComponent): Promise<MenuItemOptions[]>
}

View File

@@ -1,64 +0,0 @@
import * as fs from 'mz/fs'
import * as path from 'path'
import { Injectable } from '@angular/core'
import { TerminalColorSchemeProvider } from './api/colorSchemeProvider'
import { TerminalColorScheme } from './api/interfaces'
/** @hidden */
@Injectable()
export class HyperColorSchemes extends TerminalColorSchemeProvider {
async getSchemes (): Promise<TerminalColorScheme[]> {
const pluginsPath = path.join(process.env.HOME!, '.hyper_plugins', 'node_modules')
if (!await fs.exists(pluginsPath)) {
return []
}
const plugins = await fs.readdir(pluginsPath)
const themes: TerminalColorScheme[] = []
plugins.forEach(plugin => {
try {
const module = (global as any).require(path.join(pluginsPath, plugin))
if (module.decorateConfig) {
let config: any = {}
try {
config = module.decorateConfig({})
} catch {
console.warn('Could not load Hyper theme:', plugin)
return
}
if (config.colors) {
themes.push({
name: plugin,
foreground: config.foregroundColor,
background: config.backgroundColor,
cursor: config.cursorColor,
colors: config.colors.black ? [
config.colors.black,
config.colors.red,
config.colors.green,
config.colors.yellow,
config.colors.blue,
config.colors.magenta,
config.colors.cyan,
config.colors.white,
config.colors.lightBlack,
config.colors.lightRed,
config.colors.lightGreen,
config.colors.lightYellow,
config.colors.lightBlue,
config.colors.lightMagenta,
config.colors.lightCyan,
config.colors.lightWhite,
] : config.colors,
})
}
}
} catch (err) {
console.debug('Skipping Hyper plugin', plugin, err)
}
})
return themes
}
}

View File

@@ -2,11 +2,9 @@
import { Observable } from 'rxjs'
import { debounce } from 'utils-decorators/dist/cjs'
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
import { exec } from 'mz/child_process'
const fontManager = require('fontmanager-redux') // eslint-disable-line
import { Component } from '@angular/core'
import { ConfigService, HostAppService, Platform, getCSSFontFamily } from 'terminus-core'
import { ConfigService, getCSSFontFamily, PlatformService } from 'terminus-core'
/** @hidden */
@Component({
@@ -17,26 +15,12 @@ export class AppearanceSettingsTabComponent {
fonts: string[] = []
constructor (
private hostApp: HostAppService,
public config: ConfigService,
private platform: PlatformService,
) { }
async ngOnInit () {
if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) {
const fonts = await new Promise<any[]>((resolve) => fontManager.findFonts({ monospace: true }, resolve))
this.fonts = fonts.map(x => x.family.trim())
this.fonts.sort()
}
if (this.hostApp.platform === Platform.Linux) {
exec('fc-list :spacing=mono').then(([stdout, _]) => {
this.fonts = stdout.toString()
.split('\n')
.filter(x => !!x)
.map(x => x.split(':')[1].trim())
.map(x => x.split(',')[0].trim())
this.fonts.sort()
})
}
this.fonts = await this.platform.listFonts()
}
fontAutocomplete = (text$: Observable<string>) => {

View File

@@ -1,6 +1,6 @@
h3.mb-3 Terminal
.form-line
.form-line(*ngIf='hostApp.platform !== Platform.Web')
.header
.title Frontend
.description Switches terminal frontend implementation (experimental)
@@ -86,7 +86,7 @@ h3.mb-3 Terminal
(ngModelChange)='config.save()',
)
.form-line
.form-line(*ngIf='hostApp.platform !== Platform.Web')
.header
.title Auto-open a terminal on app start

View File

@@ -1,19 +1,22 @@
import { execFile } from 'mz/child_process'
import { Component } from '@angular/core'
import { ConfigService, ElectronService } from 'terminus-core'
import { ConfigService, HostAppService, Platform, PlatformService } from 'terminus-core'
/** @hidden */
@Component({
template: require('./terminalSettingsTab.component.pug'),
})
export class TerminalSettingsTabComponent {
Platform = Platform
constructor (
public config: ConfigService,
private electron: ElectronService,
public hostApp: HostAppService,
private platform: PlatformService,
) { }
openWSLVolumeMixer (): void {
this.electron.shell.openPath('sndvol.exe')
this.platform.openPath('sndvol.exe')
execFile('wsl.exe', ['tput', 'bel'])
}
}

View File

@@ -1,6 +1,6 @@
import { Injector } from '@angular/core'
import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs'
import { ResizeEvent } from '../api/interfaces'
import { ConfigService, ThemesService, HotkeysService } from 'terminus-core'
export interface SearchOptions {
regex?: boolean
@@ -13,10 +13,6 @@ export interface SearchOptions {
* Extend to add support for a different VT frontend implementation
*/
export abstract class Frontend {
configService: ConfigService
themesService: ThemesService
hotkeysService: HotkeysService
enableResizing = true
protected ready = new AsyncSubject<void>()
protected title = new ReplaySubject<string>(1)
@@ -40,6 +36,8 @@ export abstract class Frontend {
get dragOver$ (): Observable<DragEvent> { return this.dragOver }
get drop$ (): Observable<DragEvent> { return this.drop }
constructor (protected injector: Injector) { }
destroy (): void {
for (const o of [
this.ready,

View File

@@ -1,6 +1,7 @@
import { Injector } from '@angular/core'
import { ConfigService, getCSSFontFamily, ThemesService } from 'terminus-core'
import { Frontend, SearchOptions } from './frontend'
import { hterm, preferenceManager } from './hterm'
import { getCSSFontFamily } from 'terminus-core'
/** @hidden */
export class HTermFrontend extends Frontend {
@@ -13,6 +14,15 @@ export class HTermFrontend extends Frontend {
private configuredBackgroundColor = 'transparent'
private zoom = 0
private configService: ConfigService
private themesService: ThemesService
constructor (injector: Injector) {
super(injector)
this.configService = injector.get(ConfigService)
this.themesService = injector.get(ThemesService)
}
async attach (host: HTMLElement): Promise<void> {
if (!this.initialized) {
this.init()

View File

@@ -1,4 +1,5 @@
import { getCSSFontFamily } from 'terminus-core'
import { Injector } from '@angular/core'
import { ConfigService, getCSSFontFamily, HostAppService, HotkeysService, Platform, PlatformService } from 'terminus-core'
import { Frontend, SearchOptions } from './frontend'
import { Terminal, ITheme } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
@@ -39,8 +40,18 @@ export class XTermFrontend extends Frontend {
private opened = false
private resizeObserver?: any
constructor () {
super()
private configService: ConfigService
private hotkeysService: HotkeysService
private platformService: PlatformService
private hostApp: HostAppService
constructor (injector: Injector) {
super(injector)
this.configService = injector.get(ConfigService)
this.hotkeysService = injector.get(HotkeysService)
this.platformService = injector.get(PlatformService)
this.hostApp = injector.get(HostAppService)
this.xterm = new Terminal({
allowTransparency: true,
windowsMode: process.platform === 'win32',
@@ -88,9 +99,11 @@ export class XTermFrontend extends Frontend {
}
this.xterm.attachCustomKeyEventHandler((event: KeyboardEvent) => {
if (event.getModifierState('Meta') && event.key.toLowerCase() === 'v') {
event.preventDefault()
return false
if (this.hostApp.platform !== Platform.Web) {
if (event.getModifierState('Meta') && event.key.toLowerCase() === 'v') {
event.preventDefault()
return false
}
}
if (event.getModifierState('Meta') && event.key.startsWith('Arrow')) {
return false
@@ -167,6 +180,10 @@ export class XTermFrontend extends Frontend {
host.addEventListener('mousedown', event => this.mouseEvent.next(event))
host.addEventListener('mouseup', event => this.mouseEvent.next(event))
host.addEventListener('mousewheel', event => this.mouseEvent.next(event as MouseEvent))
host.addEventListener('contextmenu', event => {
event.preventDefault()
event.stopPropagation()
})
this.resizeObserver = new window['ResizeObserver'](() => setTimeout(() => this.resizeHandler()))
this.resizeObserver.observe(host)
@@ -190,12 +207,12 @@ export class XTermFrontend extends Frontend {
copySelection (): void {
const text = this.getSelection()
if (text.length < 1024 * 32) {
require('@electron/remote').clipboard.write({
this.platformService.setClipboard({
text: this.getSelection(),
html: this.getSelectionAsHTML(),
})
} else {
require('@electron/remote').clipboard.write({
this.platformService.setClipboard({
text: this.getSelection(),
})
}

View File

@@ -25,7 +25,6 @@ import { PathDropDecorator } from './features/pathDrop'
import { ZModemDecorator } from './features/zmodem'
import { TerminalConfigProvider } from './config'
import { TerminalHotkeyProvider } from './hotkeys'
import { HyperColorSchemes } from './colorSchemes'
import { CopyPasteContextMenu, LegacyContextMenu } from './tabContextMenu'
import { hterm } from './frontends/hterm'
@@ -50,7 +49,6 @@ import { TerminalCLIHandler } from './cli'
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
{ provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
{ provide: TerminalDecorator, useClass: ZModemDecorator, multi: true },
{ provide: TerminalDecorator, useClass: DebugDecorator, multi: true },

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { ConfigService, ThemesService, HotkeysService } from 'terminus-core'
import { Injectable, Injector } from '@angular/core'
import { ConfigService } from 'terminus-core'
import { Frontend } from '../frontends/frontend'
import { HTermFrontend } from '../frontends/htermFrontend'
import { XTermFrontend, XTermWebGLFrontend } from '../frontends/xtermFrontend'
@@ -12,8 +12,7 @@ export class TerminalFrontendService {
/** @hidden */
private constructor (
private config: ConfigService,
private themes: ThemesService,
private hotkeys: HotkeysService,
private injector: Injector,
) { }
getFrontend (session?: BaseSession|null): Frontend {
@@ -22,10 +21,7 @@ export class TerminalFrontendService {
xterm: XTermFrontend,
'xterm-webgl': XTermWebGLFrontend,
hterm: HTermFrontend,
}[this.config.store.terminal.frontend]()
frontend.configService = this.config
frontend.themesService = this.themes
frontend.hotkeysService = this.hotkeys
}[this.config.store.terminal.frontend](this.injector)
return frontend
}
if (!this.containers.has(session)) {

View File

@@ -1,6 +1,5 @@
import { MenuItemConstructorOptions } from 'electron'
import { Injectable, NgZone, Optional, Inject } from '@angular/core'
import { BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, NotificationsService } from 'terminus-core'
import { BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, NotificationsService, MenuItemOptions } from 'terminus-core'
import { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
@@ -16,7 +15,7 @@ export class CopyPasteContextMenu extends TabContextMenuItemProvider {
super()
}
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
if (tabHeader) {
return []
}
@@ -56,12 +55,12 @@ export class LegacyContextMenu extends TabContextMenuItemProvider {
super()
}
async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
if (!this.contextMenuProviders) {
return []
}
if (tab instanceof BaseTerminalTabComponent) {
let items: MenuItemConstructorOptions[] = []
let items: MenuItemOptions[] = []
for (const p of this.contextMenuProviders) {
items = items.concat(await p.getItems(tab))
}