mirror of
https://github.com/Eugeny/tabby.git
synced 2024-11-22 03:26:09 +03:00
bundle the clickable-links plugin with Tabby
This commit is contained in:
parent
3eaf46e09d
commit
bbb02f4e64
6
app/src/pluginBlacklist.ts
Normal file
6
app/src/pluginBlacklist.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const PLUGIN_BLACKLIST = [
|
||||
'terminus-shell-selector', // superseded by profiles
|
||||
'terminus-scrollbar', // now useless
|
||||
'terminus-clickable-links', // now bundled with Tabby
|
||||
'tabby-clickable-links', // now bundled with Tabby
|
||||
]
|
@ -2,6 +2,7 @@ import * as fs from 'mz/fs'
|
||||
import * as path from 'path'
|
||||
import * as remote from '@electron/remote'
|
||||
import { PluginInfo } from '../../tabby-core/src/api/mainProcess'
|
||||
import { PLUGIN_BLACKLIST } from './pluginBlacklist'
|
||||
|
||||
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
|
||||
@ -109,7 +110,7 @@ export async function findPlugins (): Promise<PluginInfo[]> {
|
||||
})
|
||||
}
|
||||
for (const packageName of pluginNames) {
|
||||
if (packageName.startsWith(PREFIX) || packageName.startsWith(LEGACY_PREFIX)) {
|
||||
if ((packageName.startsWith(PREFIX) || packageName.startsWith(LEGACY_PREFIX)) && !PLUGIN_BLACKLIST.includes(packageName)) {
|
||||
candidateLocations.push({ pluginDir, packageName })
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@
|
||||
"start": "cross-env TABBY_DEV=1 electron app --debug --inspect",
|
||||
"start:prod": "electron app --debug",
|
||||
"prod": "cross-env TABBY_DEV=1 electron app",
|
||||
"docs": "typedoc --out docs/api --tsconfig tabby-core/tsconfig.typings.json tabby-core/src/index.ts && typedoc --out docs/api/terminal --tsconfig tabby-terminal/tsconfig.typings.json tabby-terminal/src/index.ts && typedoc --out docs/api/local --tsconfig tabby-local/tsconfig.typings.json tabby-local/src/index.ts && typedoc --out docs/api/settings --tsconfig tabby-settings/tsconfig.typings.json tabby-settings/src/index.ts",
|
||||
"docs": "node scripts/build-docs.js",
|
||||
"lint": "eslint --ext ts */src */lib",
|
||||
"postinstall": "patch-package && node ./scripts/install-deps.js"
|
||||
},
|
||||
|
9
scripts/build-docs.js
Executable file
9
scripts/build-docs.js
Executable file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
const sh = require('shelljs')
|
||||
const vars = require('./vars')
|
||||
const log = require('npmlog')
|
||||
|
||||
vars.packagesWithDocs.forEach(([dest, src]) => {
|
||||
log.info('docs', src)
|
||||
sh.exec(`yarn typedoc --out docs/api/${dest} --tsconfig ${src}/tsconfig.typings.json ${src}/src/index.ts`)
|
||||
})
|
@ -25,6 +25,14 @@ exports.builtinPlugins = [
|
||||
'tabby-electron',
|
||||
'tabby-local',
|
||||
'tabby-plugin-manager',
|
||||
'tabby-linkifier',
|
||||
]
|
||||
|
||||
exports.packagesWithDocs = [
|
||||
['.', 'tabby-core'],
|
||||
['terminal', 'tabby-terminal'],
|
||||
['local', 'tabby-local'],
|
||||
['settings', 'tabby-settings'],
|
||||
]
|
||||
|
||||
exports.allPackages = [
|
||||
|
@ -1,7 +1,13 @@
|
||||
Tabby Core Plugin
|
||||
--------------------
|
||||
# Tabby Core Plugin
|
||||
|
||||
See also: [Settings plugin API](./settings/), [Terminal plugin API](./terminal/), [Local terminal API](./local/)
|
||||
See also:
|
||||
|
||||
* [Settings plugin API](./settings/)
|
||||
* [Terminal plugin API](./terminal/)
|
||||
* [Local terminal API](./local/)
|
||||
* [Linkifier plugin API](./linkifier/)
|
||||
|
||||
This module provides:
|
||||
|
||||
* tabbed interface services
|
||||
* toolbar UI
|
||||
|
@ -25,7 +25,7 @@ export { DockingService, Screen } from '../services/docking.service'
|
||||
export { Logger, ConsoleLogger, LogService } from '../services/log.service'
|
||||
export { HomeBaseService } from '../services/homeBase.service'
|
||||
export { HotkeysService } from '../services/hotkeys.service'
|
||||
export { KeyEventData, KeyName, Keystroke } from '../services/hotkeys.util'
|
||||
export { KeyEventData, KeyName, Keystroke, altKeyName, metaKeyName } from '../services/hotkeys.util'
|
||||
export { NotificationsService } from '../services/notifications.service'
|
||||
export { ThemesService } from '../services/themes.service'
|
||||
export { ProfilesService } from '../services/profiles.service'
|
||||
|
3
tabby-linkifier/README.md
Normal file
3
tabby-linkifier/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Tabby Linkifier Plugin
|
||||
|
||||
This plugin makes URLs, IPs and file paths in the terminal clickable and adds a context menu that allows quickly copying them.
|
22
tabby-linkifier/package.json
Normal file
22
tabby-linkifier/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "tabby-linkifier",
|
||||
"version": "1.0.165-nightly.0",
|
||||
"description": "Makes URLs, IPs and file paths clickable in Tabby",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"typings": "typings/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "webpack --progress --color --display-modules",
|
||||
"watch": "webpack --progress --color --watch"
|
||||
},
|
||||
"files": [
|
||||
"typings"
|
||||
],
|
||||
"author": "Eugene Pankov",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"untildify": "^4.0.0"
|
||||
}
|
||||
}
|
16
tabby-linkifier/src/api.ts
Normal file
16
tabby-linkifier/src/api.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
||||
|
||||
export abstract class LinkHandler {
|
||||
regex: RegExp
|
||||
priority = 1
|
||||
|
||||
convert (uri: string, _tab?: BaseTerminalTabComponent): Promise<string>|string {
|
||||
return uri
|
||||
}
|
||||
|
||||
verify (_uri: string, _tab?: BaseTerminalTabComponent): Promise<boolean>|boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
abstract handle (uri: string, tab?: BaseTerminalTabComponent): void
|
||||
}
|
12
tabby-linkifier/src/config.ts
Normal file
12
tabby-linkifier/src/config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ConfigProvider } from 'tabby-core'
|
||||
|
||||
/** @hidden */
|
||||
export class ClickableLinksConfigProvider extends ConfigProvider {
|
||||
defaults = {
|
||||
clickableLinks: {
|
||||
modifier: null,
|
||||
},
|
||||
}
|
||||
|
||||
platformDefaults = { }
|
||||
}
|
67
tabby-linkifier/src/decorator.ts
Normal file
67
tabby-linkifier/src/decorator.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { ConfigService, PlatformService } from 'tabby-core'
|
||||
import { TerminalDecorator, BaseTerminalTabComponent } from 'tabby-terminal'
|
||||
|
||||
import { LinkHandler } from './api'
|
||||
|
||||
@Injectable()
|
||||
export class LinkHighlighterDecorator extends TerminalDecorator {
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
private platform: PlatformService,
|
||||
@Inject(LinkHandler) private handlers: LinkHandler[],
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
attach (tab: BaseTerminalTabComponent): void {
|
||||
if (!(tab.frontend as any).xterm) {
|
||||
// not hterm
|
||||
return
|
||||
}
|
||||
|
||||
for (let handler of this.handlers) {
|
||||
const getLink = async uri => handler.convert(uri, tab)
|
||||
const openLink = async uri => handler.handle(await getLink(uri), tab)
|
||||
|
||||
;(tab.frontend as any).xterm.registerLinkMatcher(
|
||||
handler.regex,
|
||||
(event: MouseEvent, uri: string) => {
|
||||
if (!this.willHandleEvent(event)) {
|
||||
return
|
||||
}
|
||||
openLink(uri)
|
||||
},
|
||||
{
|
||||
priority: handler.priority,
|
||||
validationCallback: async (uri: string, callback: (isValid: boolean) => void) => {
|
||||
callback(await handler.verify(await handler.convert(uri, tab), tab))
|
||||
},
|
||||
willLinkActivate: (event: MouseEvent, uri: string) => {
|
||||
if (event.button === 2) {
|
||||
this.platform.popupContextMenu([
|
||||
{
|
||||
click: () => openLink(uri),
|
||||
label: 'Open',
|
||||
},
|
||||
{
|
||||
click: async () => {
|
||||
this.platform.setClipboard({ text: await getLink(uri) })
|
||||
},
|
||||
label: 'Copy',
|
||||
},
|
||||
])
|
||||
return false
|
||||
}
|
||||
return this.willHandleEvent(event)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private willHandleEvent (event: MouseEvent) {
|
||||
const modifier = this.config.store.clickableLinks.modifier
|
||||
return !modifier || event[modifier]
|
||||
}
|
||||
}
|
108
tabby-linkifier/src/handlers.ts
Normal file
108
tabby-linkifier/src/handlers.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import * as fs from 'fs/promises'
|
||||
import * as path from 'path'
|
||||
import untildify from 'untildify'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ToastrService } from 'ngx-toastr'
|
||||
import { PlatformService } from 'tabby-core'
|
||||
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
||||
|
||||
import { LinkHandler } from './api'
|
||||
|
||||
@Injectable()
|
||||
export class URLHandler extends LinkHandler {
|
||||
// From https://urlregex.com/
|
||||
regex = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{1,5})|([0-9]{1,4})))?(?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/
|
||||
|
||||
priority = 5
|
||||
|
||||
constructor (private platform: PlatformService) {
|
||||
super()
|
||||
}
|
||||
|
||||
handle (uri: string) {
|
||||
this.platform.openExternal(uri)
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class IPHandler extends LinkHandler {
|
||||
regex = /\b((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/
|
||||
|
||||
priority = 4
|
||||
|
||||
constructor (private platform: PlatformService) {
|
||||
super()
|
||||
}
|
||||
|
||||
handle (uri: string) {
|
||||
this.platform.openExternal(`http://${uri}`)
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseFileHandler extends LinkHandler {
|
||||
constructor (
|
||||
protected toastr: ToastrService,
|
||||
protected platform: PlatformService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async handle (uri: string) {
|
||||
try {
|
||||
await this.platform.openExternal('file://' + uri)
|
||||
} catch (err) {
|
||||
this.toastr.error(err.toString())
|
||||
}
|
||||
}
|
||||
|
||||
async verify (uri: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(uri)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async convert (uri: string, tab?: BaseTerminalTabComponent): Promise<string> {
|
||||
let p = untildify(uri)
|
||||
if (!path.isAbsolute(p) && tab) {
|
||||
const cwd = await tab.session?.getWorkingDirectory()
|
||||
if (cwd) {
|
||||
p = path.resolve(cwd, p)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UnixFileHandler extends BaseFileHandler {
|
||||
// Only absolute and home paths
|
||||
regex = /[~]?(\/[\w\d.~-]{1,100})+/
|
||||
|
||||
constructor (
|
||||
protected toastr: ToastrService,
|
||||
protected platform: PlatformService,
|
||||
) {
|
||||
super(toastr, platform)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class WindowsFileHandler extends BaseFileHandler {
|
||||
regex = /(([a-zA-Z]:|\\|~)\\[\w\-()\\\.]{1,1024}|"([a-zA-Z]:|\\)\\[\w\s\-()\\\.]{1,1024}")/
|
||||
|
||||
constructor (
|
||||
protected toastr: ToastrService,
|
||||
protected platform: PlatformService,
|
||||
) {
|
||||
super(toastr, platform)
|
||||
}
|
||||
|
||||
convert (uri: string, tab?: BaseTerminalTabComponent): Promise<string> {
|
||||
const sanitizedUri = uri.replace(/"/g, '')
|
||||
return super.convert(sanitizedUri, tab)
|
||||
}
|
||||
}
|
26
tabby-linkifier/src/index.ts
Normal file
26
tabby-linkifier/src/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { ToastrModule } from 'ngx-toastr'
|
||||
import { ConfigProvider } from 'tabby-core'
|
||||
import { TerminalDecorator } from 'tabby-terminal'
|
||||
|
||||
import { LinkHandler } from './api'
|
||||
import { UnixFileHandler, WindowsFileHandler, URLHandler, IPHandler } from './handlers'
|
||||
import { LinkHighlighterDecorator } from './decorator'
|
||||
import { ClickableLinksConfigProvider } from './config'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ToastrModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: LinkHandler, useClass: URLHandler, multi: true },
|
||||
{ provide: LinkHandler, useClass: IPHandler, multi: true },
|
||||
{ provide: LinkHandler, useClass: UnixFileHandler, multi: true },
|
||||
{ provide: LinkHandler, useClass: WindowsFileHandler, multi: true },
|
||||
{ provide: TerminalDecorator, useClass: LinkHighlighterDecorator, multi: true },
|
||||
{ provide: ConfigProvider, useClass: ClickableLinksConfigProvider, multi: true },
|
||||
],
|
||||
})
|
||||
export default class LinkifierModule { }
|
||||
|
||||
export * from './api'
|
7
tabby-linkifier/tsconfig.json
Normal file
7
tabby-linkifier/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
}
|
||||
}
|
14
tabby-linkifier/tsconfig.typings.json
Normal file
14
tabby-linkifier/tsconfig.typings.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"exclude": ["node_modules", "dist", "typings"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"emitDeclarationOnly": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "./typings",
|
||||
"paths": {
|
||||
"tabby-*": ["../../tabby-*"],
|
||||
"*": ["../../app/node_modules/*"]
|
||||
}
|
||||
}
|
||||
}
|
5
tabby-linkifier/webpack.config.js
Normal file
5
tabby-linkifier/webpack.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
const config = require('../webpack.plugin.config')
|
||||
module.exports = config({
|
||||
name: 'linkifier',
|
||||
dirname: __dirname,
|
||||
})
|
8
tabby-linkifier/yarn.lock
Normal file
8
tabby-linkifier/yarn.lock
Normal file
@ -0,0 +1,8 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
untildify@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
|
||||
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
|
@ -1,5 +1,4 @@
|
||||
Tabby Local Plugin
|
||||
---------------------
|
||||
# Tabby Local Plugin
|
||||
|
||||
* local shells
|
||||
|
||||
|
@ -3,13 +3,10 @@ import { compare as semverCompare } from 'semver'
|
||||
import { Observable, from, forkJoin, map } from 'rxjs'
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { Logger, LogService, PlatformService, BOOTSTRAP_DATA, BootstrapData, PluginInfo } from 'tabby-core'
|
||||
import { PLUGIN_BLACKLIST } from '../../../app/src/pluginBlacklist'
|
||||
|
||||
const OFFICIAL_NPM_ACCOUNT = 'eugenepankov'
|
||||
|
||||
const BLACKLIST = [
|
||||
'terminus-shell-selector', // superseded by profiles
|
||||
'terminus-scrollbar', // now useless
|
||||
]
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PluginManagerService {
|
||||
@ -69,7 +66,7 @@ export class PluginManagerService {
|
||||
}))
|
||||
),
|
||||
map(plugins => plugins.filter(x => x.packageName.startsWith(namePrefix))),
|
||||
map(plugins => plugins.filter(x => !BLACKLIST.includes(x.packageName))),
|
||||
map(plugins => plugins.filter(x => !PLUGIN_BLACKLIST.includes(x.packageName))),
|
||||
map(plugins => {
|
||||
const mapping: Record<string, PluginInfo[]> = {}
|
||||
for (const p of plugins) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
Tabby Settings Plugin
|
||||
------------------------
|
||||
# Tabby Settings Plugin
|
||||
|
||||
* tabbed settings interface
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
> .nav {
|
||||
padding: 20px 10px;
|
||||
width: 212px;
|
||||
width: 222px;
|
||||
flex: none;
|
||||
overflow-y: auto;
|
||||
flex-wrap: nowrap;
|
||||
|
@ -1,5 +1,4 @@
|
||||
Tabby Terminal Plugin
|
||||
------------------------
|
||||
# Tabby Terminal Plugin
|
||||
|
||||
* terminal tabs
|
||||
* terminal frontends
|
||||
|
@ -1,169 +1,203 @@
|
||||
h3.mb-3 Terminal
|
||||
div
|
||||
h3.mb-3 Rendering
|
||||
|
||||
.form-line(*ngIf='hostApp.platform !== Platform.Web')
|
||||
.header
|
||||
.title Frontend
|
||||
.description Switches terminal frontend implementation (experimental)
|
||||
.form-line(*ngIf='hostApp.platform !== Platform.Web')
|
||||
.header
|
||||
.title Frontend
|
||||
.description Switches terminal frontend implementation (experimental)
|
||||
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.frontend',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
option(value='xterm') xterm
|
||||
option(value='xterm-webgl') xterm (WebGL)
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.frontend',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
option(value='xterm') xterm
|
||||
option(value='xterm-webgl') xterm (WebGL)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Terminal bell
|
||||
.btn-group(
|
||||
[(ngModel)]='config.store.terminal.bell',
|
||||
(ngModelChange)='config.save()',
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"off"'
|
||||
)
|
||||
| Off
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"visual"'
|
||||
)
|
||||
| Visual
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"audible"'
|
||||
)
|
||||
| Audible
|
||||
div.mt-4
|
||||
h3 Keyboard
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.bell != "audible" && (config.store.terminal.profile || "").startsWith("wsl")')
|
||||
.mr-auto WSL terminal bell can only be muted via Volume Mixer
|
||||
button.btn.btn-secondary((click)='openWSLVolumeMixer()') Show Mixer
|
||||
.form-line
|
||||
.header
|
||||
.title Use {{altKeyName}} as the Meta key
|
||||
.description Lets the shell handle Meta key instead of OS
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.altIsMeta',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Right click
|
||||
.description(*ngIf='config.store.terminal.rightClick == "paste"') Long-click for context menu
|
||||
.btn-group(
|
||||
[(ngModel)]='config.store.terminal.rightClick',
|
||||
(ngModelChange)='config.save()',
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
value='off'
|
||||
)
|
||||
| Off
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
value='menu'
|
||||
)
|
||||
| Context menu
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
value='paste'
|
||||
)
|
||||
| Paste
|
||||
.form-line
|
||||
.header
|
||||
.title Scroll on input
|
||||
.description Scrolls the terminal to the bottom on user input
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.scrollOnInput',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Paste on middle-click
|
||||
div.mt-4
|
||||
h3 Mouse
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.pasteOnMiddleClick',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.form-line
|
||||
.header
|
||||
.title Right click
|
||||
.description(*ngIf='config.store.terminal.rightClick == "paste"') Long-click for context menu
|
||||
.btn-group(
|
||||
[(ngModel)]='config.store.terminal.rightClick',
|
||||
(ngModelChange)='config.save()',
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
value='off'
|
||||
)
|
||||
| Off
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
value='menu'
|
||||
)
|
||||
| Context menu
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
value='paste'
|
||||
)
|
||||
| Paste
|
||||
|
||||
.form-line(*ngIf='hostApp.platform !== Platform.Web')
|
||||
.header
|
||||
.title Auto-open a terminal on app start
|
||||
.form-line
|
||||
.header
|
||||
.title Paste on middle-click
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.autoOpen',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.pasteOnMiddleClick',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Restore terminal tabs on app start
|
||||
.form-line
|
||||
.header
|
||||
.title Word separators
|
||||
.description Double-click selection will stop at these characters
|
||||
input.form-control(
|
||||
type='text',
|
||||
placeholder=' ()[]{}\'"',
|
||||
[(ngModel)]='config.store.terminal.wordSeparator',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.recoverTabs',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.form-line
|
||||
.header
|
||||
.title Require a key to click links
|
||||
.description When enabled, links are only clickable while holding this key
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Bracketed paste (requires shell support)
|
||||
.description Prevents accidental execution of pasted commands
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.bracketedPaste',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.clickableLinks.modifier',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
option([value]='null') None
|
||||
option(value='ctrlKey') Ctrl
|
||||
option(value='altKey') {{altKeyName}}
|
||||
option(value='shiftKey') Shift
|
||||
option(value='metaKey') {{metaKeyName}}
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Copy on select
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.copyOnSelect',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
div.mt-4
|
||||
h3 Clipboard
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Scroll on input
|
||||
.description Scrolls the terminal to the bottom on user input
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.scrollOnInput',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.form-line
|
||||
.header
|
||||
.title Copy on select
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.copyOnSelect',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Use Alt key as the Meta key
|
||||
.description Lets the shell handle Meta key instead of OS
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.altIsMeta',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.form-line
|
||||
.header
|
||||
.title Bracketed paste (requires shell support)
|
||||
.description Prevents accidental execution of pasted commands
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.bracketedPaste',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Word separators
|
||||
.description Double-click selection will stop at these characters
|
||||
input.form-control(
|
||||
type='text',
|
||||
placeholder=' ()[]{}\'"',
|
||||
[(ngModel)]='config.store.terminal.wordSeparator',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.form-line
|
||||
.header
|
||||
.title Warn on multi-line paste
|
||||
.description Show a confirmation box when pasting multiple lines
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.warnOnMultilinePaste',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Warn on multi-line paste
|
||||
.description Show a confirmation box when pasting multiple lines
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.warnOnMultilinePaste',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
div.mt-4
|
||||
h3 Sound
|
||||
|
||||
.form-line(*ngIf='hostApp.platform === Platform.Windows')
|
||||
.header
|
||||
.title Set Tabby as %COMSPEC%
|
||||
.description Allows opening .bat files in tabs, but breaks some shells
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.setComSpec',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.form-line
|
||||
.header
|
||||
.title Terminal bell
|
||||
.btn-group(
|
||||
[(ngModel)]='config.store.terminal.bell',
|
||||
(ngModelChange)='config.save()',
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"off"'
|
||||
)
|
||||
| Off
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"visual"'
|
||||
)
|
||||
| Visual
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"audible"'
|
||||
)
|
||||
| Audible
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.bell != "audible" && (config.store.terminal.profile || "").startsWith("wsl")')
|
||||
.mr-auto WSL terminal bell can only be muted via Volume Mixer
|
||||
button.btn.btn-secondary((click)='openWSLVolumeMixer()') Show Mixer
|
||||
|
||||
div.mt-4
|
||||
h3 Startup
|
||||
|
||||
.form-line(*ngIf='hostApp.platform !== Platform.Web')
|
||||
.header
|
||||
.title Auto-open a terminal on app start
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.autoOpen',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Restore terminal tabs on app start
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.recoverTabs',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
div.mt-4(*ngIf='hostApp.platform === Platform.Windows')
|
||||
h3 Windows
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Set Tabby as %COMSPEC%
|
||||
.description Allows opening .bat files in tabs, but breaks some shells
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.setComSpec',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Component, HostBinding } from '@angular/core'
|
||||
import { ConfigService, HostAppService, Platform, PlatformService } from 'tabby-core'
|
||||
import { ConfigService, HostAppService, Platform, PlatformService, altKeyName, metaKeyName } from 'tabby-core'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
@ -7,6 +7,8 @@ import { ConfigService, HostAppService, Platform, PlatformService } from 'tabby-
|
||||
})
|
||||
export class TerminalSettingsTabComponent {
|
||||
Platform = Platform
|
||||
altKeyName = altKeyName
|
||||
metaKeyName = metaKeyName
|
||||
|
||||
@HostBinding('class.content-box') true
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user