color scheme previews (fixes #2286)

This commit is contained in:
Eugene Pankov 2020-03-22 23:17:25 +01:00
parent c87a1b92d3
commit 795979be07
14 changed files with 353 additions and 233 deletions

View File

@ -262,6 +262,29 @@ ngb-tabset .tab-content {
}
}
.list-group-light {
.list-group-item {
background: transparent;
border: none;
border-top: 1px solid rgba(255, 255, 255, .1);
&:not(.combi) {
padding: $list-group-item-padding-y $list-group-item-padding-x;
}
&:first-child {
border-top: none;
}
&.list-group-item-action {
&:hover, &.active {
background: $list-group-hover-bg;
}
}
}
}
checkbox i.on {
color: $blue;
}
@ -392,3 +415,7 @@ search-panel {
border-color: $nav-tabs-link-active-border-color;
}
}
hr {
border-color: $list-group-border-color;
}

View File

@ -1,6 +1,6 @@
h3.mb-3 Appearance
.d-flex
.mr-5
.mr-5.w-100
.form-line
.header
.title Font
@ -27,148 +27,7 @@ h3.mb-3 Appearance
(ngModelChange)='config.save()',
)
.form-line(*ngIf='!editingColorScheme')
.header
.title Color scheme
.input-group.w-50
select.form-control(
[compareWith]='equalComparator',
[(ngModel)]='config.store.terminal.colorScheme',
(ngModelChange)='config.save()',
)
option(*ngFor='let scheme of config.store.terminal.customColorSchemes', [ngValue]='scheme') Custom: {{scheme.name}}
option(*ngFor='let scheme of colorSchemes', [ngValue]='scheme') {{scheme.name}}
.input-group-append
button.btn.btn-secondary((click)='editScheme(config.store.terminal.colorScheme)')
i.fas.fa-pen
.input-group-append
button.btn.btn-outline-danger(
(click)='deleteScheme(config.store.terminal.colorScheme)',
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
)
i.fas.fa-trash
.form-group(*ngIf='editingColorScheme')
label Editing
.input-group
input.form-control(type='text', [(ngModel)]='editingColorScheme.name')
.input-group-append
button.btn.btn-secondary((click)='saveScheme()')
i.fas.fa-check
.input-group-append
button.btn.btn-secondary((click)='cancelEditing()')
i.fas.fa-times
.form-group(*ngIf='editingColorScheme')
color-picker(
[(model)]='editingColorScheme.foreground',
(modelChange)='config.save(); schemeChanged = true',
title='FG',
)
color-picker(
[(model)]='editingColorScheme.background',
(modelChange)='config.save(); schemeChanged = true',
title='BG',
)
color-picker(
[(model)]='editingColorScheme.cursor',
(modelChange)='config.save(); schemeChanged = true',
title='CU',
)
color-picker(
*ngFor='let _ of editingColorScheme.colors; let idx = index; trackBy: colorsTrackBy',
[(model)]='editingColorScheme.colors[idx]',
(modelChange)='config.save(); schemeChanged = true',
[title]='idx',
)
div
.form-group
.appearance-preview(
[style.font-family]='getPreviewFontFamily()',
[style.font-size]='config.store.terminal.fontSize + "px"',
[style.background-color]='(config.store.terminal.background == "theme") ? null : config.store.terminal.colorScheme.background',
[style.color]='config.store.terminal.colorScheme.foreground',
[style.font-feature-settings]='\'"liga" \' + config.store.terminal.ligatures ? 1 : 0',
[style.font-variant-ligatures]='config.store.terminal.ligatures ? "initial" : "none"',
)
div
span([style.background-color]='config.store.terminal.colorScheme.colors[0]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[1]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[2]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[3]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[4]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[5]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[6]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[7]')  
span   
span([style.color]='config.store.terminal.colorScheme.colors[0]') B
span  
span([style.color]='config.store.terminal.colorScheme.colors[1]') R
span  
span([style.color]='config.store.terminal.colorScheme.colors[2]') G
span  
span([style.color]='config.store.terminal.colorScheme.colors[3]') Y
span  
span([style.color]='config.store.terminal.colorScheme.colors[4]') B
span  
span([style.color]='config.store.terminal.colorScheme.colors[5]') M
span  
span([style.color]='config.store.terminal.colorScheme.colors[6]') T
span  
span([style.color]='config.store.terminal.colorScheme.colors[7]') W
div
span([style.background-color]='config.store.terminal.colorScheme.colors[8]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[9]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[10]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[11]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[12]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[13]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[14]')  
span([style.background-color]='config.store.terminal.colorScheme.colors[15]')  
span   
span([style.color]='config.store.terminal.colorScheme.colors[8]') B
span  
span([style.color]='config.store.terminal.colorScheme.colors[9]') R
span  
span([style.color]='config.store.terminal.colorScheme.colors[10]') G
span  
span([style.color]='config.store.terminal.colorScheme.colors[11]') Y
span  
span([style.color]='config.store.terminal.colorScheme.colors[12]') B
span  
span([style.color]='config.store.terminal.colorScheme.colors[13]') M
span  
span([style.color]='config.store.terminal.colorScheme.colors[14]') T
span  
span([style.color]='config.store.terminal.colorScheme.colors[15]') W
div
span  
div
span john@doe-pc
span([style.color]='config.store.terminal.colorScheme.colors[1]') $
span ls -l
div
span drwxr-xr-x 1 root
span([style.color]='config.store.terminal.colorScheme.colors[4]') directory 📁
div
span -rw-r--r-- 1 root файл
div
span -rwxr-xr-x 1 root
span([style.color]='config.store.terminal.colorScheme.colors[2]') 実行可能ファイル
div
span -rwxr-xr-x 1 root
span([style.color]='config.store.terminal.colorScheme.colors[6]') sym
span ->
span([style.color]='config.store.terminal.colorScheme.colors[1]') link
div
span  
div
span john@doe-pc
span([style.color]='config.store.terminal.colorScheme.colors[1]') $
span rm -rf /
span([style.background-color]='config.store.terminal.colorScheme.cursor')  
color-scheme-preview([scheme]='config.store.terminal.colorScheme', [fontPreview]='true')
.form-line
.header

View File

@ -1,12 +1,4 @@
.appearance-preview {
padding: 10px 0;
margin-left: 20px;
padding: 10px;
overflow: hidden;
max-width: 400px;
max-height: 400px;
span {
white-space: pre;
}
color-scheme-preview {
flex-shrink: 0;
margin-bottom: 20px;
}

View File

@ -2,13 +2,10 @@
import { Observable } from 'rxjs'
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
import { exec } from 'mz/child_process'
import deepEqual from 'deep-equal'
const fontManager = require('fontmanager-redux') // eslint-disable-line
import { Component, Inject } from '@angular/core'
import { ConfigService, HostAppService, Platform, ElectronService, getCSSFontFamily } from 'terminus-core'
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
import { TerminalColorScheme } from '../api/interfaces'
import { Component } from '@angular/core'
import { ConfigService, HostAppService, Platform, getCSSFontFamily } from 'terminus-core'
/** @hidden */
@Component({
@ -17,15 +14,9 @@ import { TerminalColorScheme } from '../api/interfaces'
})
export class AppearanceSettingsTabComponent {
fonts: string[] = []
colorSchemes: TerminalColorScheme[] = []
equalComparator = deepEqual
editingColorScheme: TerminalColorScheme|null = null
schemeChanged = false
constructor (
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
private hostApp: HostAppService,
private electron: ElectronService,
public config: ConfigService,
) { }
@ -49,7 +40,6 @@ export class AppearanceSettingsTabComponent {
this.fonts.sort()
})
}
this.colorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
}
fontAutocomplete = (text$: Observable<string>) => {
@ -61,49 +51,6 @@ export class AppearanceSettingsTabComponent {
)
}
editScheme (scheme: TerminalColorScheme) {
this.editingColorScheme = scheme
this.schemeChanged = false
}
saveScheme () {
let schemes = this.config.store.terminal.customColorSchemes
schemes = schemes.filter(x => x !== this.editingColorScheme && x.name !== this.editingColorScheme!.name)
schemes.push(this.editingColorScheme)
this.config.store.terminal.customColorSchemes = schemes
this.config.save()
this.cancelEditing()
}
cancelEditing () {
this.editingColorScheme = null
}
async deleteScheme (scheme: TerminalColorScheme) {
if ((await this.electron.showMessageBox(
this.hostApp.getWindow(),
{
type: 'warning',
message: `Delete "${scheme.name}"?`,
buttons: ['Keep', 'Delete'],
defaultId: 1,
}
)).response === 1) {
let schemes = this.config.store.terminal.customColorSchemes
schemes = schemes.filter(x => x !== scheme)
this.config.store.terminal.customColorSchemes = schemes
this.config.save()
}
}
isCustomScheme (scheme: TerminalColorScheme) {
return this.config.store.terminal.customColorSchemes.some(x => deepEqual(x, scheme))
}
colorsTrackBy (index) {
return index
}
getPreviewFontFamily () {
return getCSSFontFamily(this.config.store)
}

View File

@ -9,6 +9,7 @@ div(
[ngbPopover]='content',
[style.background]='model',
(click)='open()',
autoClose='outside',
container='body',
#popover='ngbPopover',
) {{ title }}

View File

@ -1,4 +1,4 @@
import { Component, Input, Output, EventEmitter, HostListener, ViewChild } from '@angular/core'
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
/** @hidden */
@ -12,34 +12,14 @@ export class ColorPickerComponent {
@Input() title: string
@Output() modelChange = new EventEmitter<string>()
@ViewChild('popover') popover: NgbPopover
@ViewChild('input') input
isOpen: boolean
open (): void {
setImmediate(() => {
this.popover.open()
setImmediate(() => {
this.input.nativeElement.focus()
this.isOpen = true
})
this.popover['_windowRef'].location.nativeElement.querySelector('input').focus()
})
}
@HostListener('document:click', ['$event']) onOutsideClick ($event: MouseEvent): void {
if (!this.isOpen) {
return
}
const windowRef = (this.popover as any)._windowRef
if (!windowRef) {
return
}
if ($event.target !== windowRef.location.nativeElement &&
!windowRef.location.nativeElement.contains($event.target)) {
this.popover.close()
this.isOpen = false
}
}
onChange (): void {
this.modelChange.emit(this.model)
}

View File

@ -0,0 +1,35 @@
.preview(
[style.font-family]='getPreviewFontFamily()',
[style.font-size]='(fontPreview ? config.store.terminal.fontSize : 11) + "px"',
[style.background-color]='scheme.background',
[style.color]='scheme.foreground',
[style.font-feature-settings]='\'"liga" \' + config.store.terminal.ligatures ? 1 : 0',
[style.font-variant-ligatures]='config.store.terminal.ligatures ? "initial" : "none"',
)
div
span([style.color]='scheme.colors[2]') john
span([style.color]='scheme.colors[6]') @
span([style.color]='scheme.colors[4]') doe-pc
strong([style.color]='scheme.colors[1]') $
span ls
span([style.background-color]='scheme.cursor') &nbsp;
div
span -rwxr-xr-x 1 root
strong([style.color]='scheme.colors[3]') Documents
div
span -rwxr-xr-x 1 root
strong([style.color]='scheme.colors[5]') Downloads
div
span -rwxr-xr-x 1 root
strong([style.color]='scheme.colors[13]') Pictures
div
span -rwxr-xr-x 1 root
strong([style.color]='scheme.colors[12]') Music
div(*ngIf='fontPreview')
span -rwxr-xr-x 1 root
span([style.color]='scheme.colors[2]') 実行可能ファイル
div(*ngIf='fontPreview')
span -rwxr-xr-x 1 root
span([style.color]='scheme.colors[6]') sym
span ->
span([style.color]='scheme.colors[1]') link

View File

@ -0,0 +1,15 @@
:host {
display: block;
}
.preview {
margin-top: 10px;
margin-left: 10px;
padding: 5px 10px;
border-radius: 4px;
overflow: hidden;
span {
white-space: pre;
}
}

View File

@ -0,0 +1,21 @@
import { Component, Input, ChangeDetectionStrategy } from '@angular/core'
import { ConfigService, getCSSFontFamily } from 'terminus-core'
import { TerminalColorScheme } from '../api/interfaces'
/** @hidden */
@Component({
selector: 'color-scheme-preview',
template: require('./colorSchemePreview.component.pug'),
styles: [require('./colorSchemePreview.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ColorSchemePreviewComponent {
@Input() scheme: TerminalColorScheme
@Input() fontPreview = false
constructor (public config: ConfigService) {}
getPreviewFontFamily (): string {
return getCSSFontFamily(this.config.store)
}
}

View File

@ -0,0 +1,96 @@
.head
h3.mb-3 Current color scheme
.d-flex.align-items-center(*ngIf='!editing')
span {{getCurrentSchemeName()}}
.mr-auto
.btn-toolbar
button.btn.btn-secondary((click)='editScheme()')
i.fas.fa-pen
span Edit
.mr-1
button.btn.btn-danger(
(click)='deleteScheme(config.store.terminal.colorScheme)',
*ngIf='currentCustomScheme'
)
i.fas.fa-trash
span Delete
div(*ngIf='editing')
.form-group
label Name
input.form-control(type='text', [(ngModel)]='config.store.terminal.colorScheme.name')
.form-group
color-picker(
[(model)]='config.store.terminal.colorScheme.foreground',
(modelChange)='config.save()',
title='FG',
)
color-picker(
[(model)]='config.store.terminal.colorScheme.background',
(modelChange)='config.save()',
title='BG',
)
color-picker(
[(model)]='config.store.terminal.colorScheme.cursor',
(modelChange)='config.save()',
title='CU',
)
color-picker(
*ngFor='let _ of config.store.terminal.colorScheme.colors; let idx = index; trackBy: colorsTrackBy',
[(model)]='config.store.terminal.colorScheme.colors[idx]',
(modelChange)='config.save()',
[title]='idx',
)
color-scheme-preview([scheme]='config.store.terminal.colorScheme')
.btn-toolbar.d-flex.mt-2(*ngIf='editing')
.mr-auto
button.btn.btn-primary((click)='saveScheme()')
i.fas.fa-check
span Save
.mr-1
button.btn.btn-secondary((click)='cancelEditing()')
i.fas.fa-times
span Cancel
hr.mt-3.mb-4
.input-group.mb-3
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', placeholder='Search color schemes', [(ngModel)]='filter')
.body
.list-group-light.mb-3
ng-container(*ngFor='let scheme of allColorSchemes')
.list-group-item.list-group-item-action(
[hidden]='filter && !scheme.name.toLowerCase().includes(filter.toLowerCase())',
(click)='selectScheme(scheme)',
[class.active]='(currentCustomScheme || currentStockScheme) === scheme'
)
.d-flex.w-100.align-items-center
i.fas.fa-fw([class.fa-check]='(currentCustomScheme || currentStockScheme) === scheme')
.ml-2
.mr-auto
span {{scheme.name}}
.badge.badge-info.ml-2(*ngIf='customColorSchemes.includes(scheme)') Custom
div
.d-flex
.swatch(
*ngFor='let index of colorIndexes.slice(0, 8)',
[style.background-color]='scheme.colors[index]'
)
.d-flex
.swatch(
*ngFor='let index of colorIndexes.slice(8, 16)',
[style.background-color]='scheme.colors[index]'
)
color-scheme-preview([scheme]='scheme')

View File

@ -0,0 +1,22 @@
.head {
flex: none;
}
.body {
overflow: auto;
flex: auto;
min-height: 0;
}
.swatch {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 3px;
margin-bottom: 3px;
box-shadow: 0 1px 1px rgba(0, 0, 0, .5);
}
.list-group-item color-scheme-preview {
margin-left: 14px;
}

View File

@ -0,0 +1,106 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import deepEqual from 'deep-equal'
import { Component, Inject, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'
import { ConfigService, HostAppService, ElectronService } from 'terminus-core'
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
import { TerminalColorScheme } from '../api/interfaces'
/** @hidden */
@Component({
template: require('./colorSchemeSettingsTab.component.pug'),
styles: [require('./colorSchemeSettingsTab.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ColorSchemeSettingsTabComponent {
@Input() stockColorSchemes: TerminalColorScheme[] = []
@Input() customColorSchemes: TerminalColorScheme[] = []
@Input() allColorSchemes: TerminalColorScheme[] = []
@Input() filter = ''
@Input() editing = false
colorIndexes = [...new Array(16).keys()]
currentStockScheme: TerminalColorScheme|null = null
currentCustomScheme: TerminalColorScheme|null = null
constructor (
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
private changeDetector: ChangeDetectorRef,
private hostApp: HostAppService,
private electron: ElectronService,
public config: ConfigService,
) { }
async ngOnInit () {
this.stockColorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
this.stockColorSchemes.sort((a, b) => a.name.localeCompare(b.name))
this.customColorSchemes = this.config.store.terminal.customColorSchemes
this.changeDetector.markForCheck()
this.update()
}
ngOnChanges () {
this.update()
}
selectScheme (scheme: TerminalColorScheme) {
this.config.store.terminal.colorScheme = { ...scheme }
this.config.save()
this.cancelEditing()
this.update()
}
update () {
this.currentCustomScheme = this.findMatchingScheme(this.config.store.terminal.colorScheme, this.customColorSchemes)
this.currentStockScheme = this.findMatchingScheme(this.config.store.terminal.colorScheme, this.stockColorSchemes)
this.allColorSchemes = this.customColorSchemes.concat(this.stockColorSchemes)
this.changeDetector.markForCheck()
}
editScheme () {
this.editing = true
}
saveScheme () {
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== this.config.store.terminal.colorScheme.name)
this.customColorSchemes.push(this.config.store.terminal.colorScheme)
this.config.store.terminal.customColorSchemes = this.customColorSchemes
this.config.save()
this.cancelEditing()
this.update()
}
cancelEditing () {
this.editing = false
}
async deleteScheme (scheme: TerminalColorScheme) {
if ((await this.electron.showMessageBox(
this.hostApp.getWindow(),
{
type: 'warning',
message: `Delete "${scheme.name}"?`,
buttons: ['Keep', 'Delete'],
defaultId: 1,
}
)).response === 1) {
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== scheme.name)
this.config.store.terminal.customColorSchemes = this.customColorSchemes
this.config.save()
this.update()
}
}
getCurrentSchemeName () {
return (this.currentCustomScheme || this.currentStockScheme)?.name || 'Custom'
}
findMatchingScheme (scheme: TerminalColorScheme, schemes: TerminalColorScheme[]) {
return schemes.find(x => deepEqual(x, scheme)) || null
}
colorsTrackBy (index) {
return index
}
}

View File

@ -11,10 +11,12 @@ import TerminusCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryP
import { SettingsTabProvider } from 'terminus-settings'
import { AppearanceSettingsTabComponent } from './components/appearanceSettingsTab.component'
import { ColorSchemeSettingsTabComponent } from './components/colorSchemeSettingsTab.component'
import { TerminalTabComponent } from './components/terminalTab.component'
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
import { ColorPickerComponent } from './components/colorPicker.component'
import { ColorSchemePreviewComponent } from './components/colorSchemePreview.component'
import { EditProfileModalComponent } from './components/editProfileModal.component'
import { EnvironmentEditorComponent } from './components/environmentEditor.component'
import { SearchPanelComponent } from './components/searchPanel.component'
@ -30,7 +32,7 @@ import { TerminalDecorator } from './api/decorator'
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
import { TerminalColorSchemeProvider } from './api/colorSchemeProvider'
import { ShellProvider } from './api/shellProvider'
import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ShellSettingsTabProvider } from './settings'
import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ColorSchemeSettingsTabProvider, ShellSettingsTabProvider } from './settings'
import { DebugDecorator } from './features/debug'
import { PathDropDecorator } from './features/pathDrop'
import { ZModemDecorator } from './features/zmodem'
@ -68,6 +70,7 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
],
providers: [
{ provide: SettingsTabProvider, useClass: AppearanceSettingsTabProvider, multi: true },
{ provide: SettingsTabProvider, useClass: ColorSchemeSettingsTabProvider, multi: true },
{ provide: SettingsTabProvider, useClass: ShellSettingsTabProvider, multi: true },
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
@ -106,14 +109,17 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
entryComponents: [
TerminalTabComponent,
AppearanceSettingsTabComponent,
ColorSchemeSettingsTabComponent,
ShellSettingsTabComponent,
TerminalSettingsTabComponent,
EditProfileModalComponent,
] as any[],
declarations: [
ColorPickerComponent,
ColorSchemePreviewComponent,
TerminalTabComponent,
AppearanceSettingsTabComponent,
ColorSchemeSettingsTabComponent,
ShellSettingsTabComponent,
TerminalSettingsTabComponent,
EditProfileModalComponent,

View File

@ -4,12 +4,13 @@ import { SettingsTabProvider } from 'terminus-settings'
import { AppearanceSettingsTabComponent } from './components/appearanceSettingsTab.component'
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
import { ColorSchemeSettingsTabComponent } from './components/colorSchemeSettingsTab.component'
/** @hidden */
@Injectable()
export class AppearanceSettingsTabProvider extends SettingsTabProvider {
id = 'terminal-appearance'
icon = 'palette'
icon = 'swatchbook'
title = 'Appearance'
getComponentType (): any {
@ -17,6 +18,18 @@ export class AppearanceSettingsTabProvider extends SettingsTabProvider {
}
}
/** @hidden */
@Injectable()
export class ColorSchemeSettingsTabProvider extends SettingsTabProvider {
id = 'terminal-color-scheme'
icon = 'palette'
title = 'Color Scheme'
getComponentType (): any {
return ColorSchemeSettingsTabComponent
}
}
/** @hidden */
@Injectable()
export class ShellSettingsTabProvider extends SettingsTabProvider {