mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-23 17:51:50 +00:00
Compare commits
127 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b9763044ee | ||
![]() |
46e0035327 | ||
![]() |
6df8707b6d | ||
![]() |
24b7922539 | ||
![]() |
485665d449 | ||
![]() |
e09a011c23 | ||
![]() |
833a348fdb | ||
![]() |
26ff6f17e7 | ||
![]() |
356a2f38b6 | ||
![]() |
bdb37a9a18 | ||
![]() |
22d89041f8 | ||
![]() |
d5285cf268 | ||
![]() |
3db98aa421 | ||
![]() |
47dba5b52c | ||
![]() |
72874a1e84 | ||
![]() |
fc1deb67e8 | ||
![]() |
d0bb3c731c | ||
![]() |
13e54a46d7 | ||
![]() |
55a975bc8b | ||
![]() |
a62752efec | ||
![]() |
4e97ce5117 | ||
![]() |
19a5f2dc2d | ||
![]() |
52433afd13 | ||
![]() |
e1d9f50426 | ||
![]() |
5837c61ac4 | ||
![]() |
8c03e5b1aa | ||
![]() |
66074e3eb6 | ||
![]() |
221746f3e7 | ||
![]() |
2c59e30c39 | ||
![]() |
4e13a601cc | ||
![]() |
a159890cba | ||
![]() |
4534eefc1d | ||
![]() |
e06c44f973 | ||
![]() |
c1616b1a7a | ||
![]() |
5b34fa9371 | ||
![]() |
956a923ea2 | ||
![]() |
224abcb2c4 | ||
![]() |
e8af224f7b | ||
![]() |
a1d39563c3 | ||
![]() |
621536a078 | ||
![]() |
3cf9353d08 | ||
![]() |
6a2fa7efa9 | ||
![]() |
6b0fe0e2d1 | ||
![]() |
923876dc23 | ||
![]() |
233ae9cbe6 | ||
![]() |
09daf8102d | ||
![]() |
fd7893e9f8 | ||
![]() |
cf5c3e71f6 | ||
![]() |
5c30bbb7e4 | ||
![]() |
afce339187 | ||
![]() |
d528d1148b | ||
![]() |
786b31e2a2 | ||
![]() |
0ad32fa79d | ||
![]() |
93a89e3c86 | ||
![]() |
4e667edf9f | ||
![]() |
403bafe0a2 | ||
![]() |
40209dc60d | ||
![]() |
1ccd1df6e1 | ||
![]() |
800c1fa039 | ||
![]() |
0a67987e3c | ||
![]() |
6c7a8092a4 | ||
![]() |
9f87886a9b | ||
![]() |
0aa5df421d | ||
![]() |
d3498a6a46 | ||
![]() |
41a53a3e8e | ||
![]() |
636942ff86 | ||
![]() |
d1b874c191 | ||
![]() |
89f369abe6 | ||
![]() |
f7cea92900 | ||
![]() |
9a611709d0 | ||
![]() |
2094409d23 | ||
![]() |
2b061d3f77 | ||
![]() |
ed06c78a16 | ||
![]() |
36a6af05c4 | ||
![]() |
64b4eed9c3 | ||
![]() |
afc9270846 | ||
![]() |
2ea5edc101 | ||
![]() |
869a7e866c | ||
![]() |
1bd3b5301b | ||
![]() |
4ff66a39d8 | ||
![]() |
a3857d5dc8 | ||
![]() |
bf762cc4c7 | ||
![]() |
461cd2bec7 | ||
![]() |
da599567f8 | ||
![]() |
7f921c4d34 | ||
![]() |
0eb006f297 | ||
![]() |
07095f3476 | ||
![]() |
59b283067b | ||
![]() |
3635eee77d | ||
![]() |
64df798dc1 | ||
![]() |
f091206e37 | ||
![]() |
3148927395 | ||
![]() |
fb39bfd560 | ||
![]() |
f8cd9fcea7 | ||
![]() |
f1148cc47f | ||
![]() |
ffcc0d549a | ||
![]() |
861dd8ef86 | ||
![]() |
1e5e46eae1 | ||
![]() |
d644eec56e | ||
![]() |
a656699afd | ||
![]() |
93db8fa046 | ||
![]() |
05669046e9 | ||
![]() |
025d2d1748 | ||
![]() |
e17ba8c351 | ||
![]() |
d3dee44475 | ||
![]() |
0f0699d46a | ||
![]() |
b7540e59a8 | ||
![]() |
827345d899 | ||
![]() |
59de67ca58 | ||
![]() |
ccad826ca9 | ||
![]() |
838acc0a23 | ||
![]() |
804ae44ec8 | ||
![]() |
5f04c3b74b | ||
![]() |
4afd49e38c | ||
![]() |
ba32c69001 | ||
![]() |
e9a3947488 | ||
![]() |
b2f4b44123 | ||
![]() |
f4eacc1d66 | ||
![]() |
9155104662 | ||
![]() |
cbbd38ca83 | ||
![]() |
e5cf72e79b | ||
![]() |
a71d9c0727 | ||
![]() |
26e2c60265 | ||
![]() |
a5a0546e68 | ||
![]() |
92b34fbc08 | ||
![]() |
38b7e44f64 | ||
![]() |
3aa957a3f5 |
@@ -406,6 +406,24 @@
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "logicmachine123",
|
||||
"name": "Logic Machine",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/63876444?v=4",
|
||||
"profile": "https://git.io/JnP49",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "cypherbits",
|
||||
"name": "cypherbits",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/10424900?v=4",
|
||||
"profile": "https://github.com/cypherbits",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
@@ -36,7 +36,9 @@ rules:
|
||||
'@typescript-eslint/prefer-readonly': off
|
||||
'@typescript-eslint/require-await': off
|
||||
'@typescript-eslint/strict-boolean-expressions': off
|
||||
'@typescript-eslint/no-misused-promises': off
|
||||
'@typescript-eslint/no-misused-promises':
|
||||
- error
|
||||
- checksVoidReturn: false
|
||||
'@typescript-eslint/typedef': off
|
||||
'@typescript-eslint/consistent-type-imports': off
|
||||
'@typescript-eslint/sort-type-union-intersection-members': off
|
||||
@@ -95,7 +97,9 @@ rules:
|
||||
- error
|
||||
- single
|
||||
- allowTemplateLiterals: true
|
||||
'@typescript-eslint/no-confusing-void-expression': off
|
||||
'@typescript-eslint/no-confusing-void-expression':
|
||||
- error
|
||||
- ignoreArrowShorthand: true
|
||||
'@typescript-eslint/no-non-null-assertion': off
|
||||
'@typescript-eslint/no-unnecessary-condition':
|
||||
- error
|
||||
@@ -116,3 +120,4 @@ rules:
|
||||
'@typescript-eslint/no-var-requires': off
|
||||
'@typescript-eslint/no-unsafe-argument': off
|
||||
'@typescript-eslint/restrict-plus-operands': off
|
||||
'@typescript-eslint/space-infix-ops': off
|
||||
|
300
.github/workflows/build.yml
vendored
Normal file
300
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
name: Package-Build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
Lint:
|
||||
runs-on: macos-11.0
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.2.0
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
npm i -g yarn@1.19.1
|
||||
cd app
|
||||
yarn
|
||||
cd ..
|
||||
rm app/node_modules/.yarn-integrity
|
||||
yarn
|
||||
|
||||
- name: Build typings
|
||||
run: yarn run build:typings
|
||||
|
||||
- name: Lint
|
||||
run: yarn run lint
|
||||
|
||||
macOS-Build:
|
||||
runs-on: macos-11.0
|
||||
needs: Lint
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: x86_64
|
||||
- arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.2.0
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo npm i -g yarn@1.22.1
|
||||
cd app
|
||||
yarn
|
||||
cd ..
|
||||
rm app/node_modules/.yarn-integrity
|
||||
yarn
|
||||
|
||||
- name: Build native deps
|
||||
run: scripts/build-native.js
|
||||
env:
|
||||
ARCH: ${{matrix.arch}}
|
||||
|
||||
- name: Build native deps
|
||||
run: |
|
||||
rm -rf app/node_modules/cpu-features
|
||||
rm -rf app/node_modules/ssh2/crypto/build
|
||||
if: ${{ matrix.arch == 'arm64' }}
|
||||
|
||||
- name: Webpack
|
||||
run: yarn run build
|
||||
|
||||
- name: Prepackage plugins
|
||||
run: scripts/prepackage-plugins.js
|
||||
env:
|
||||
ARCH: ${{matrix.arch}}
|
||||
|
||||
- run: sed -i '' 's/updateInfo = await/\/\/updateInfo = await/g' node_modules/app-builder-lib/out/targets/ArchiveTarget.js
|
||||
|
||||
- name: Build and sign packages
|
||||
run: scripts/build-macos.js
|
||||
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags'))
|
||||
env:
|
||||
ARCH: ${{matrix.arch}}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPSTORE_USERNAME: ${{ secrets.APPSTORE_USERNAME }}
|
||||
APPSTORE_PASSWORD: ${{ secrets.APPSTORE_PASSWORD }}
|
||||
USE_HARD_LINKS: false
|
||||
# DEBUG: electron-builder,electron-builder:*
|
||||
|
||||
- name: Build packages without signing
|
||||
run: scripts/build-macos.js
|
||||
if: "! (github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')))"
|
||||
env:
|
||||
ARCH: ${{matrix.arch}}
|
||||
# DEBUG: electron-builder,electron-builder:*
|
||||
|
||||
- name: Upload symbols
|
||||
run: |
|
||||
sudo npm install -g @sentry/cli --unsafe-perm
|
||||
./scripts/sentry-upload.js
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir artifact-pkg
|
||||
mv dist/*.pkg artifact-pkg/
|
||||
mkdir artifact-zip
|
||||
mv dist/*.zip artifact-zip/
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload PKG
|
||||
with:
|
||||
name: macOS .pkg (${{matrix.arch}})
|
||||
path: artifact-pkg
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload ZIP
|
||||
with:
|
||||
name: macOS .zip (${{matrix.arch}})
|
||||
path: artifact-zip
|
||||
|
||||
Linux-Build:
|
||||
runs-on: ubuntu-18.04
|
||||
needs: Lint
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2.2.0
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install bsdtar zsh
|
||||
npm i -g yarn@1.19.1
|
||||
cd app
|
||||
yarn
|
||||
cd ..
|
||||
rm app/node_modules/.yarn-integrity
|
||||
yarn
|
||||
npm run patch
|
||||
|
||||
- name: Build native deps
|
||||
run: scripts/build-native.js
|
||||
|
||||
- name: Webpack
|
||||
run: yarn run build
|
||||
|
||||
- name: Prepackage plugins
|
||||
run: scripts/prepackage-plugins.js
|
||||
|
||||
- name: Build packages
|
||||
run: scripts/build-linux.js
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
USE_HARD_LINKS: false
|
||||
# DEBUG: electron-builder,electron-builder:*
|
||||
|
||||
- name: Build web resources
|
||||
run: zsh -c 'tar czf tabby-web.tar.gz (tabby-*|web)/dist'
|
||||
|
||||
- name: Upload symbols
|
||||
run: |
|
||||
sudo npm install -g @sentry/cli --unsafe-perm
|
||||
./scripts/sentry-upload.js
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir artifact-deb
|
||||
mv dist/*.deb artifact-deb/ || true
|
||||
mkdir artifact-rpm
|
||||
mv dist/*.rpm artifact-rpm/ || true
|
||||
mkdir artifact-pacman
|
||||
mv dist/*.pacman artifact-pacman/ || true
|
||||
mkdir artifact-snap
|
||||
mv dist/*.snap artifact-snap/ || true
|
||||
mkdir artifact-tar.gz
|
||||
mv dist/*.tar.gz artifact-tar.gz/ || true
|
||||
mkdir artifact-web
|
||||
mv tabby-web.tar.gz artifact-web/ || true
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload DEB
|
||||
with:
|
||||
name: Linux DEB
|
||||
path: artifact-deb
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload RPM
|
||||
with:
|
||||
name: Linux RPM
|
||||
path: artifact-rpm
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload Pacman Package
|
||||
with:
|
||||
name: Linux Pacman
|
||||
path: artifact-pacman
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload Snap
|
||||
with:
|
||||
name: Linux Snap
|
||||
path: artifact-snap
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload Linux tarball
|
||||
with:
|
||||
name: Linux tarball
|
||||
path: artifact-tar.gz
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload web tarball
|
||||
with:
|
||||
name: Web tarball
|
||||
path: artifact-web
|
||||
|
||||
Windows-Build:
|
||||
runs-on: windows-2016
|
||||
needs: Lint
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.2.0
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Build
|
||||
shell: powershell
|
||||
run: |
|
||||
npm i -g yarn@1.19.1
|
||||
yarn
|
||||
node scripts/build-native.js
|
||||
yarn run build
|
||||
node scripts/prepackage-plugins.js
|
||||
|
||||
- name: Build and sign packages
|
||||
run: node scripts/build-windows.js
|
||||
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags'))
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
|
||||
# DEBUG: electron-builder,electron-builder:*
|
||||
|
||||
- name: Build packages without signing
|
||||
run: node scripts/build-windows.js
|
||||
if: "!(github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')))"
|
||||
|
||||
- name: Upload symbols
|
||||
run: |
|
||||
npm install @sentry/cli
|
||||
node scripts/sentry-upload.js
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir artifact-setup
|
||||
mv dist/*-setup.exe artifact-setup/
|
||||
mkdir artifact-portable
|
||||
mv dist/*-portable.zip artifact-portable/
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload installer
|
||||
with:
|
||||
name: Windows installer
|
||||
path: artifact-setup
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload portable build
|
||||
with:
|
||||
name: Windows portable build
|
||||
path: artifact-portable
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.1.5
|
||||
uses: actions/setup-node@v2.2.0
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
|
31
.github/workflows/lint.yml
vendored
31
.github/workflows/lint.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Lint
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
npm i -g yarn@1.19.1
|
||||
cd app
|
||||
yarn
|
||||
cd ..
|
||||
rm app/node_modules/.yarn-integrity
|
||||
yarn
|
||||
|
||||
- name: Build typings
|
||||
run: yarn run build:typings
|
||||
|
||||
- name: Lint
|
||||
run: yarn run lint
|
107
.github/workflows/linux.yml
vendored
107
.github/workflows/linux.yml
vendored
@@ -1,107 +0,0 @@
|
||||
name: Linux Build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install bsdtar zsh
|
||||
npm i -g yarn@1.19.1
|
||||
cd app
|
||||
yarn
|
||||
cd ..
|
||||
rm app/node_modules/.yarn-integrity
|
||||
yarn
|
||||
npm run patch
|
||||
|
||||
- name: Build native deps
|
||||
run: scripts/build-native.js
|
||||
|
||||
- name: Webpack
|
||||
run: yarn run build
|
||||
|
||||
- name: Prepackage plugins
|
||||
run: scripts/prepackage-plugins.js
|
||||
|
||||
- name: Build packages
|
||||
run: scripts/build-linux.js
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
USE_HARD_LINKS: false
|
||||
# DEBUG: electron-builder,electron-builder:*
|
||||
|
||||
- name: Build web resources
|
||||
run: zsh -c 'tar czf tabby-web.tar.gz (tabby-*|web)/dist'
|
||||
|
||||
- name: Upload symbols
|
||||
run: |
|
||||
sudo npm install -g @sentry/cli --unsafe-perm
|
||||
./scripts/sentry-upload.js
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir artifact-deb
|
||||
mv dist/*.deb artifact-deb/ || true
|
||||
mkdir artifact-rpm
|
||||
mv dist/*.rpm artifact-rpm/ || true
|
||||
mkdir artifact-pacman
|
||||
mv dist/*.pacman artifact-pacman/ || true
|
||||
mkdir artifact-snap
|
||||
mv dist/*.snap artifact-snap/ || true
|
||||
mkdir artifact-tar.gz
|
||||
mv dist/*.tar.gz artifact-tar.gz/ || true
|
||||
mkdir artifact-web
|
||||
mv tabby-web.tar.gz artifact-web/ || true
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload DEB
|
||||
with:
|
||||
name: Linux DEB
|
||||
path: artifact-deb
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload RPM
|
||||
with:
|
||||
name: Linux RPM
|
||||
path: artifact-rpm
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload Pacman Package
|
||||
with:
|
||||
name: Linux Pacman
|
||||
path: artifact-pacman
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload Snap
|
||||
with:
|
||||
name: Linux Snap
|
||||
path: artifact-snap
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload Linux tarball
|
||||
with:
|
||||
name: Linux tarball
|
||||
path: artifact-tar.gz
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload web tarball
|
||||
with:
|
||||
name: Web tarball
|
||||
path: artifact-web
|
99
.github/workflows/macos.yml
vendored
99
.github/workflows/macos.yml
vendored
@@ -1,99 +0,0 @@
|
||||
name: macOS Build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-11.0
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: x86_64
|
||||
- arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo npm i -g yarn@1.22.1
|
||||
cd app
|
||||
yarn
|
||||
cd ..
|
||||
rm app/node_modules/.yarn-integrity
|
||||
yarn
|
||||
|
||||
- name: Build native deps
|
||||
run: scripts/build-native.js
|
||||
env:
|
||||
ARCH: ${{matrix.arch}}
|
||||
|
||||
- name: Build native deps
|
||||
run: |
|
||||
rm -rf app/node_modules/cpu-features
|
||||
rm -rf app/node_modules/ssh2/crypto/build
|
||||
if: ${{ matrix.arch == 'arm64' }}
|
||||
|
||||
- name: Webpack
|
||||
run: yarn run build
|
||||
|
||||
- name: Prepackage plugins
|
||||
run: scripts/prepackage-plugins.js
|
||||
env:
|
||||
ARCH: ${{matrix.arch}}
|
||||
|
||||
- run: sed -i '' 's/updateInfo = await/\/\/updateInfo = await/g' node_modules/app-builder-lib/out/targets/ArchiveTarget.js
|
||||
|
||||
- name: Build and sign packages
|
||||
run: scripts/build-macos.js
|
||||
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags'))
|
||||
env:
|
||||
ARCH: ${{matrix.arch}}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPSTORE_USERNAME: ${{ secrets.APPSTORE_USERNAME }}
|
||||
APPSTORE_PASSWORD: ${{ secrets.APPSTORE_PASSWORD }}
|
||||
USE_HARD_LINKS: false
|
||||
# DEBUG: electron-builder,electron-builder:*
|
||||
|
||||
- name: Build packages without signing
|
||||
run: scripts/build-macos.js
|
||||
if: "! (github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')))"
|
||||
env:
|
||||
ARCH: ${{matrix.arch}}
|
||||
# DEBUG: electron-builder,electron-builder:*
|
||||
|
||||
- name: Upload symbols
|
||||
run: |
|
||||
sudo npm install -g @sentry/cli --unsafe-perm
|
||||
./scripts/sentry-upload.js
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir artifact-pkg
|
||||
mv dist/*.pkg artifact-pkg/
|
||||
mkdir artifact-zip
|
||||
mv dist/*.zip artifact-zip/
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload PKG
|
||||
with:
|
||||
name: macOS .pkg (${{matrix.arch}})
|
||||
path: artifact-pkg
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload ZIP
|
||||
with:
|
||||
name: macOS .zip (${{matrix.arch}})
|
||||
path: artifact-zip
|
66
.github/workflows/windows.yml
vendored
66
.github/workflows/windows.yml
vendored
@@ -1,66 +0,0 @@
|
||||
name: Windows Build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2016
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Build
|
||||
shell: powershell
|
||||
run: |
|
||||
npm i -g yarn@1.19.1
|
||||
yarn
|
||||
node scripts/build-native.js
|
||||
yarn run build
|
||||
node scripts/prepackage-plugins.js
|
||||
|
||||
- name: Build and sign packages
|
||||
run: node scripts/build-windows.js
|
||||
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags'))
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
|
||||
# DEBUG: electron-builder,electron-builder:*
|
||||
|
||||
- name: Build packages without signing
|
||||
run: node scripts/build-windows.js
|
||||
if: "!(github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')))"
|
||||
|
||||
- name: Upload symbols
|
||||
run: |
|
||||
npm install @sentry/cli
|
||||
node scripts/sentry-upload.js
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir artifact-setup
|
||||
mv dist/*-setup.exe artifact-setup/
|
||||
mkdir artifact-portable
|
||||
mv dist/*-portable.zip artifact-portable/
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload installer
|
||||
with:
|
||||
name: Installer
|
||||
path: artifact-setup
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload portable build
|
||||
with:
|
||||
name: Portable build
|
||||
path: artifact-portable
|
@@ -15,8 +15,8 @@ yarn
|
||||
```
|
||||
|
||||
```
|
||||
# Linux (Debian here as an example)
|
||||
sudo apt install libfontconfig-dev libsecret-1-dev bsdtar libnss3 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm1
|
||||
# Linux (Debian/Ubuntu here as an example)
|
||||
sudo apt install libfontconfig-dev libsecret-1-dev libarchive-tools libnss3 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm1 cmake
|
||||
yarn
|
||||
./scripts/build-native.js
|
||||
```
|
||||
|
12
README.md
12
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=RELEASE&logo=github&style=for-the-badge"></a> <a href="https://nightly.link/Eugeny/tabby/workflows/windows/master"><img src="https://shields.io/badge/-Nightly-blue?logo=windows&style=for-the-badge"/></a> <a href="https://nightly.link/Eugeny/tabby/workflows/macos/master"><img src="https://shields.io/badge/-Nightly-black?logo=apple&style=for-the-badge"/></a> <a href="https://nightly.link/Eugeny/tabby/workflows/linux/master"><img src="https://shields.io/badge/-Nightly-orange?logo=linux&style=for-the-badge"/></a> <a href="https://gitter.im/terminus-terminal/community"><img alt="Gitter" src="https://img.shields.io/gitter/room/terminus/community.svg?color=magenta&logo=gitter&style=for-the-badge"></a>
|
||||
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=RELEASE&logo=github&style=for-the-badge"></a> <a href="https://nightly.link/Eugeny/tabby/workflows/build/master"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> <a href="https://matrix.to/#/#tabby-general:matrix.org"><img alt="Matrix" src="https://img.shields.io/matrix/tabby-general:matrix.org?logo=matrix&style=for-the-badge&color=magenta"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -95,17 +95,17 @@ Tabby will run as a portable app on Windows, if you create a `data` folder in th
|
||||
|
||||
Plugins and themes can be installed directly from the Settings view inside Tabby.
|
||||
|
||||
* [clickable-links](https://github.com/Eugeny/terminus-clickable-links) - makes paths and URLs in the terminal clickable
|
||||
* [clickable-links](https://github.com/Eugeny/tabby-clickable-links) - makes paths and URLs in the terminal clickable
|
||||
* [title-control](https://github.com/kbjr/terminus-title-control) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
|
||||
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - quickly send commands to one or all terminal tabs
|
||||
* [save-output](https://github.com/Eugeny/terminus-save-output) - record terminal output into a file
|
||||
* [save-output](https://github.com/Eugeny/tabby-save-output) - record terminal output into a file
|
||||
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to hterm tabs
|
||||
* [sync-config](https://github.com/starxg/terminus-sync-config) - sync the config to Gist or Gitee
|
||||
|
||||
<a name="themes"></a>
|
||||
# Themes
|
||||
|
||||
* [hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
||||
* [hype](https://github.com/Eugeny/tabby-theme-hype) - a Hyper inspired theme
|
||||
* [relaxed](https://github.com/Relaxed-Theme/relaxed-terminal-themes#terminus) - the Relaxed theme for Tabby
|
||||
* [gruvbox](https://github.com/porkloin/terminus-theme-gruvbox)
|
||||
* [windows10](https://www.npmjs.com/package/terminus-theme-windows10)
|
||||
@@ -184,6 +184,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ydcool"><img src="https://avatars.githubusercontent.com/u/5668295?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dominic Yin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ydcool" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bdr99"><img src="https://avatars.githubusercontent.com/u/2292715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brandon Rothweiler</b></sub></a><br /><a href="#design-bdr99" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://git.io/JnP49"><img src="https://avatars.githubusercontent.com/u/63876444?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Logic Machine</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=logicmachine123" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/cypherbits"><img src="https://avatars.githubusercontent.com/u/10424900?v=4?s=100" width="100px;" alt=""/><br /><sub><b>cypherbits</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=cypherbits" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -193,3 +195,5 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
||||
<img src="https://ga-beacon.appspot.com/UA-3278102-18/github/readme" width="1"/>
|
||||
|
@@ -16,12 +16,6 @@ export function parseArgs (argv: string[], cwd: string): any {
|
||||
.command('profile [profileName]', 'open a tab with specified profile', {
|
||||
profileName: { type: 'string' },
|
||||
})
|
||||
.command('connect-ssh [connectionName]', 'open a tab for a saved SSH connection', {
|
||||
connectionName: { type: 'string' },
|
||||
})
|
||||
.command('connect-serial [connectionName]', 'open a tab for a saved serial connection', {
|
||||
connectionName: { type: 'string' },
|
||||
})
|
||||
.command('paste [text]', 'paste stdin into the active tab', yargs => {
|
||||
return yargs.option('escape', {
|
||||
alias: 'e',
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import * as glasstron from 'glasstron'
|
||||
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { debounceTime } from 'rxjs/operators'
|
||||
import { Subject, Observable, debounceTime } from 'rxjs'
|
||||
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions } from 'electron'
|
||||
import ElectronConfig = require('electron-config')
|
||||
import * as os from 'os'
|
||||
@@ -364,7 +363,7 @@ export class Window {
|
||||
this.disableVibrancyWhileDragging = value
|
||||
})
|
||||
|
||||
let moveEndedTimeout: number|null = null
|
||||
let moveEndedTimeout: any = null
|
||||
const onBoundsChange = () => {
|
||||
if (!this.lastVibrancy?.enabled || !this.disableVibrancyWhileDragging) {
|
||||
return
|
||||
|
@@ -14,15 +14,7 @@
|
||||
"watch": "webpack --progress --color --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^12.0.0",
|
||||
"@angular/common": "^12.0.0",
|
||||
"@angular/compiler": "^12.0.0",
|
||||
"@angular/core": "^12.0.0",
|
||||
"@angular/forms": "^12.0.0",
|
||||
"@angular/platform-browser": "^12.0.0",
|
||||
"@angular/platform-browser-dynamic": "^12.0.0",
|
||||
"@electron/remote": "1.2.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^9.1.1",
|
||||
"any-promise": "^1.3.0",
|
||||
"electron-config": "2.0.0",
|
||||
"electron-debug": "^3.2.0",
|
||||
@@ -33,12 +25,11 @@
|
||||
"keytar": "^7.7.0",
|
||||
"mz": "^2.7.0",
|
||||
"native-process-working-directory": "^1.0.2",
|
||||
"ngx-toastr": "^14.0.0",
|
||||
"node-pty": "^0.10.1",
|
||||
"npm": "6",
|
||||
"rxjs": "^7.1.0",
|
||||
"yargs": "^17.0.1",
|
||||
"zone.js": "^0.11.4"
|
||||
"rxjs": "^7.2.0",
|
||||
"source-map-support": "^0.5.19",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"macos-native-processlist": "^2.0.0",
|
||||
@@ -48,11 +39,10 @@
|
||||
"windows-process-tree": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mz": "2.7.3",
|
||||
"@types/node": "15.12.5",
|
||||
"@types/mz": "2.7.4",
|
||||
"@types/node": "16.0.1",
|
||||
"ngx-filesize": "^2.0.16",
|
||||
"node-abi": "^2.30.0",
|
||||
"source-map-support": "^0.5.19"
|
||||
"node-abi": "^2.30.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tabby-community-color-schemes": "*",
|
||||
|
@@ -5,13 +5,15 @@ import 'rxjs'
|
||||
import './global.scss'
|
||||
import './toastr.scss'
|
||||
|
||||
// Importing before @angular/*
|
||||
import { findPlugins, initModuleLookup, loadPlugins } from './plugins'
|
||||
|
||||
import { enableProdMode, NgModuleRef, ApplicationRef } from '@angular/core'
|
||||
import { enableDebugTools } from '@angular/platform-browser'
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
import { getRootModule } from './app.module'
|
||||
import { findPlugins, initModuleLookup, loadPlugins } from './plugins'
|
||||
import { BootstrapData, BOOTSTRAP_DATA, PluginInfo } from '../../tabby-core/src/api/mainProcess'
|
||||
|
||||
// Always land on the start view
|
||||
|
@@ -158,3 +158,31 @@ ngb-typeahead-window {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-group-item > button {
|
||||
margin: -7px 0;
|
||||
}
|
||||
|
||||
|
||||
// Windows high contrast mode
|
||||
@media screen and (forced-colors: active) {
|
||||
.custom-switch .custom-control-label::before {
|
||||
background: buttonface;
|
||||
}
|
||||
|
||||
.custom-switch .custom-control-label::after {
|
||||
background: buttontext;
|
||||
}
|
||||
|
||||
.custom-switch .custom-control-input:checked ~ .custom-control-label::before {
|
||||
background: activetext;
|
||||
}
|
||||
|
||||
.custom-switch .custom-control-input:checked ~ .custom-control-label::after {
|
||||
background: canvas;
|
||||
}
|
||||
|
||||
color-scheme-preview, terminaltab > .content {
|
||||
forced-color-adjust: none;
|
||||
}
|
||||
}
|
||||
|
@@ -18,28 +18,47 @@ function normalizePath (p: string): string {
|
||||
|
||||
const builtinPluginsPath = process.env.TABBY_DEV ? path.dirname(remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
|
||||
|
||||
const cachedBuiltinModules = {
|
||||
'@angular/animations': require('@angular/animations'),
|
||||
'@angular/common': require('@angular/common'),
|
||||
'@angular/compiler': require('@angular/compiler'),
|
||||
'@angular/core': require('@angular/core'),
|
||||
'@angular/forms': require('@angular/forms'),
|
||||
'@angular/platform-browser': require('@angular/platform-browser'),
|
||||
'@angular/platform-browser/animations': require('@angular/platform-browser/animations'),
|
||||
'@angular/platform-browser-dynamic': require('@angular/platform-browser-dynamic'),
|
||||
'@ng-bootstrap/ng-bootstrap': require('@ng-bootstrap/ng-bootstrap'),
|
||||
'ngx-toastr': require('ngx-toastr'),
|
||||
rxjs: require('rxjs'),
|
||||
'rxjs/operators': require('rxjs/operators'),
|
||||
'zone.js/dist/zone.js': require('zone.js/dist/zone.js'),
|
||||
}
|
||||
|
||||
const builtinModules = [
|
||||
'@angular/animations',
|
||||
'@angular/common',
|
||||
'@angular/compiler',
|
||||
'@angular/core',
|
||||
'@angular/forms',
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
'@ng-bootstrap/ng-bootstrap',
|
||||
'ngx-toastr',
|
||||
'rxjs',
|
||||
'rxjs/operators',
|
||||
...Object.keys(cachedBuiltinModules),
|
||||
'tabby-core',
|
||||
'tabby-local',
|
||||
'tabby-settings',
|
||||
'tabby-terminal',
|
||||
'zone.js/dist/zone.js',
|
||||
]
|
||||
|
||||
export type ProgressCallback = (current: number, total: number) => void // eslint-disable-line @typescript-eslint/no-type-alias
|
||||
const originalRequire = (global as any).require
|
||||
;(global as any).require = function (query: string) {
|
||||
if (cachedBuiltinModules[query]) {
|
||||
return cachedBuiltinModules[query]
|
||||
}
|
||||
return originalRequire.apply(this, [query])
|
||||
}
|
||||
|
||||
const cachedBuiltinModules = {}
|
||||
const originalModuleRequire = nodeModule.prototype.require
|
||||
nodeModule.prototype.require = function (query: string) {
|
||||
if (cachedBuiltinModules[query]) {
|
||||
return cachedBuiltinModules[query]
|
||||
}
|
||||
return originalModuleRequire.call(this, query)
|
||||
}
|
||||
|
||||
export type ProgressCallback = (current: number, total: number) => void // eslint-disable-line @typescript-eslint/no-type-alias
|
||||
|
||||
export function initModuleLookup (userPluginsPath: string): void {
|
||||
global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
|
||||
@@ -57,24 +76,10 @@ export function initModuleLookup (userPluginsPath: string): void {
|
||||
}
|
||||
|
||||
builtinModules.forEach(m => {
|
||||
cachedBuiltinModules[m] = nodeRequire(m)
|
||||
if (!cachedBuiltinModules[m]) {
|
||||
cachedBuiltinModules[m] = nodeRequire(m)
|
||||
}
|
||||
})
|
||||
|
||||
const originalRequire = (global as any).require
|
||||
;(global as any).require = function (query: string) {
|
||||
if (cachedBuiltinModules[query]) {
|
||||
return cachedBuiltinModules[query]
|
||||
}
|
||||
return originalRequire.apply(this, [query])
|
||||
}
|
||||
|
||||
const originalModuleRequire = nodeModule.prototype.require
|
||||
nodeModule.prototype.require = function (query: string) {
|
||||
if (cachedBuiltinModules[query]) {
|
||||
return cachedBuiltinModules[query]
|
||||
}
|
||||
return originalModuleRequire.call(this, query)
|
||||
}
|
||||
}
|
||||
|
||||
export async function findPlugins (): Promise<PluginInfo[]> {
|
||||
|
@@ -31,9 +31,9 @@ module.exports = {
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: {
|
||||
loader: 'awesome-typescript-loader',
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||
configFile: path.resolve(__dirname, 'tsconfig.json'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -60,23 +60,13 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
'@angular/core': 'commonjs @angular/core',
|
||||
'@angular/compiler': 'commonjs @angular/compiler',
|
||||
'@angular/platform-browser': 'commonjs @angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic': 'commonjs @angular/platform-browser-dynamic',
|
||||
'@angular/forms': 'commonjs @angular/forms',
|
||||
'@angular/common': 'commonjs @angular/common',
|
||||
'@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
|
||||
'@electron/remote': 'commonjs @electron/remote',
|
||||
child_process: 'commonjs child_process',
|
||||
electron: 'commonjs electron',
|
||||
fs: 'commonjs fs',
|
||||
'ngx-toastr': 'commonjs ngx-toastr',
|
||||
module: 'commonjs module',
|
||||
mz: 'commonjs mz',
|
||||
path: 'commonjs path',
|
||||
rxjs: 'commonjs rxjs',
|
||||
'zone.js': 'commonjs zone.js/dist/zone.js',
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
|
@@ -25,9 +25,9 @@ module.exports = {
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: {
|
||||
loader: 'awesome-typescript-loader',
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFileName: path.resolve(__dirname, 'tsconfig.main.json'),
|
||||
configFile: path.resolve(__dirname, 'tsconfig.main.json'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -39,15 +39,12 @@ module.exports = {
|
||||
'electron-config': 'commonjs electron-config',
|
||||
'electron-debug': 'commonjs electron-debug',
|
||||
'electron-promise-ipc': 'commonjs electron-promise-ipc',
|
||||
'electron-vibrancy': 'commonjs electron-vibrancy',
|
||||
fs: 'commonjs fs',
|
||||
glasstron: 'commonjs glasstron',
|
||||
mz: 'commonjs mz',
|
||||
npm: 'commonjs npm',
|
||||
'node-pty': 'commonjs node-pty',
|
||||
path: 'commonjs path',
|
||||
rxjs: 'commonjs rxjs',
|
||||
'rxjs/operators': 'commonjs rxjs/operators',
|
||||
util: 'commonjs util',
|
||||
'source-map-support': 'commonjs source-map-support',
|
||||
'windows-swca': 'commonjs windows-swca',
|
||||
|
@@ -2,55 +2,6 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@angular/animations@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-12.0.0.tgz#5f845b1a58ffb6f3ea6103edf0756ac65320b725"
|
||||
integrity sha512-BG/Ksk3863I7GKUem73Kty4UeU289oN+iPo/0O0x2dJCzNcpafML0GJpz4lg/RT9l6UddFviI4q9NiopR+eJfw==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@angular/common@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-12.0.0.tgz#a4b992f3af997e9e957500148100f3f2a90ad3e9"
|
||||
integrity sha512-d6+WSnCFcxAHBsbCvBC3Rutmk+tB5CEdKhkTBY/vGe0A/MjbayzHR4IDv2i0+UZDLSgMJubqh3iCPUcSglXSEg==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@angular/compiler@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-12.0.0.tgz#bb0d4f464fee4803dbda49d862474f771c31f633"
|
||||
integrity sha512-7NdZNyxm9KLlRMmmtId6RfV6VbQIUMDxN44R+ax66BoWsuhdYXUDsDO554LwYwrjnnXXGkurDJhv7umeRwaZGw==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@angular/core@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-12.0.0.tgz#d16a217f0919b3b161229118c52b1f703815eb71"
|
||||
integrity sha512-fwXtF6qP8pr07+El/dg67RmgsI4Ubfi+E5YLjYKQ62gM8MzYyYGmLPakFzFnbzYrOr05zdprrbcVgGtMRHapMA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@angular/forms@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-12.0.0.tgz#faf5e3e36a8c4f57f42a5b3dd11786f39c94d693"
|
||||
integrity sha512-/Z2AWd2k/9cs+WwXBlZ8yUqgGsHYcp8g6PUCehZQk1gd/4n4FOKvTIGiypajGUPwO4GOHJDzibfCsGw8MenCpQ==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@angular/platform-browser-dynamic@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.0.0.tgz#295036e7b487b6dbe3b13db763a371675d391ee6"
|
||||
integrity sha512-Rkxr/KVOZGuGSuIYo2XZYbOpyS2t2jpLPS65KUUcOEwktj4hSv5VZ2soZF18tG5ZNbx06C1QDW/j9HwmZjEh5g==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@angular/platform-browser@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-12.0.0.tgz#097805ad9a5db044dc0a74c1294cdfa5122eca4c"
|
||||
integrity sha512-h+uMMluRh4dqJIor7EpvwNKRjv4xCxpttizJlqbo3vfcoOoLDoc9SvEFiXxd+UVh3S0re8zBsyBIJl+gTVFKWQ==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@electron/remote@1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-1.2.0.tgz#772eb4c3ac17aaba5a9cf05a09092f6277f5671f"
|
||||
@@ -65,13 +16,6 @@
|
||||
update-notifier "^2.2.0"
|
||||
yargs "^8.0.2"
|
||||
|
||||
"@ng-bootstrap/ng-bootstrap@^9.1.1":
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-9.1.1.tgz#5a629915ea93b4f9b4d61854cb6862d99a7c9ca4"
|
||||
integrity sha512-m31qKJylYueXm+a3YEoOfnrJYR1lovb7WgaQwvXQz3dDmtaYRX4n8aPeCMp1VrI7hFfFITKWo0GxPaI3JIFk4w==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@serialport/binding-abstract@^9.0.7":
|
||||
version "9.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@serialport/binding-abstract/-/binding-abstract-9.0.7.tgz#d2c7ecea0f100bdf20187bfc0d34ba90f5504e1e"
|
||||
@@ -143,17 +87,17 @@
|
||||
dependencies:
|
||||
debug "^4.3.1"
|
||||
|
||||
"@types/mz@2.7.3":
|
||||
version "2.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/mz/-/mz-2.7.3.tgz#e42a21e73f5f9340fe4a176981fafb1eb8cc6c12"
|
||||
integrity sha512-Zp1NUJ4Alh3gaun0a5rkF3DL7b2j1WB6rPPI5h+CJ98sQnxe9qwskClvupz/4bqChGR3L/BRhTjlaOwR+uiZJg==
|
||||
"@types/mz@2.7.4":
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/mz/-/mz-2.7.4.tgz#f9d1535cb5171199b28ae6abd6ec29e856551401"
|
||||
integrity sha512-Zs0imXxyWT20j3Z2NwKpr0IO2LmLactBblNyLua5Az4UHuqOQ02V3jPTgyKwDkuc33/ahw+C3O1PIZdrhFMuQA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@15.12.5":
|
||||
version "15.12.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.5.tgz#9a78318a45d75c9523d2396131bd3cca54b2d185"
|
||||
integrity sha512-se3yX7UHv5Bscf8f1ERKvQOD6sTyycH3hdaoozvaLxgUiY5lIGEeH37AD0G0Qi9kPqihPn0HOfd2yaIEN9VwEg==
|
||||
"@types/node@*", "@types/node@16.0.1":
|
||||
version "16.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.1.tgz#70cedfda26af7a2ca073fdcc9beb2fff4aa693f8"
|
||||
integrity sha512-hBOx4SUlEPKwRi6PrXuTGw1z6lz0fjsibcWCM378YxsSu/6+C30L6CR49zIBKHiwNWCYIcOLjg4OHKZaFeLAug==
|
||||
|
||||
JSONStream@^1.3.4, JSONStream@^1.3.5:
|
||||
version "1.3.5"
|
||||
@@ -2099,13 +2043,6 @@ ngx-filesize@^2.0.16:
|
||||
filesize ">= 4.0.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
ngx-toastr@^14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-14.0.0.tgz#20e4737ef330b892a453768cd98b980558aeb286"
|
||||
integrity sha512-dnDzSY73pF6FvNyxdh6ftfvXvUg6SU7MAT3orPUCzA77t3ZcFslro06zk4NCA2g67RF7dBwM0OJ/y0SN6fdGYw==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
node-abi@^2.20.0, node-abi@^2.30.0, node-abi@^2.7.0:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b"
|
||||
@@ -3016,10 +2953,10 @@ run-queue@^1.0.0, run-queue@^1.0.3:
|
||||
dependencies:
|
||||
aproba "^1.1.1"
|
||||
|
||||
rxjs@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.1.0.tgz#94202d27b19305ef7b1a4f330277b2065df7039e"
|
||||
integrity sha512-gCFO5iHIbRPwznl6hAYuwNFld8W4S2shtSJIqG27ReWXo9IWrCyEICxUA+6vJHwSR/OakoenC4QsDxq50tzYmw==
|
||||
rxjs@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.2.0.tgz#5cd12409639e9514a71c9f5f9192b2c4ae94de31"
|
||||
integrity sha512-aX8w9OpKrQmiPKfT1bqETtUr9JygIz6GZ+gql8v7CijClsP0laoFUdKzxFAoWuRdSlOdU2+crss+cMf+cqMTnw==
|
||||
dependencies:
|
||||
tslib "~2.1.0"
|
||||
|
||||
@@ -3457,7 +3394,7 @@ tough-cookie@~2.5.0:
|
||||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tslib@^2.0.0, tslib@^2.1.0:
|
||||
tslib@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
|
||||
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
|
||||
@@ -3820,10 +3757,3 @@ yargs@^8.0.2:
|
||||
which-module "^2.0.0"
|
||||
y18n "^3.2.1"
|
||||
yargs-parser "^7.0.0"
|
||||
|
||||
zone.js@^0.11.4:
|
||||
version "0.11.4"
|
||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.4.tgz#0f70dcf6aba80f698af5735cbb257969396e8025"
|
||||
integrity sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
48
package.json
48
package.json
@@ -1,30 +1,38 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@angular/animations": "^12.0.0",
|
||||
"@angular/common": "^12.0.0",
|
||||
"@angular/compiler": "^12.0.0",
|
||||
"@angular/core": "^12.0.0",
|
||||
"@angular/forms": "^12.0.0",
|
||||
"@angular/platform-browser": "^12.0.0",
|
||||
"@angular/platform-browser-dynamic": "^12.0.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"@sentry/cli": "^1.64.2",
|
||||
"@sentry/electron": "^2.5.0",
|
||||
"@terminus-term/to-string-loader": "1.1.7-beta.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
|
||||
"@sentry/cli": "^1.67.1",
|
||||
"@sentry/electron": "^2.5.1",
|
||||
"@tabby-gang/to-string-loader": "^1.1.7-beta.2",
|
||||
"@types/electron-config": "^3.2.2",
|
||||
"@types/electron-debug": "^2.1.0",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/js-yaml": "^4.0.1",
|
||||
"@types/node": "15.12.5",
|
||||
"@types/webpack-env": "^1.16.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||
"@typescript-eslint/parser": "^4.28.0",
|
||||
"@types/fs-extra": "^9.0.12",
|
||||
"@types/js-yaml": "^4.0.2",
|
||||
"@types/node": "16.0.1",
|
||||
"@types/webpack-env": "^1.16.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"apply-loader": "2.0.0",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"clone-deep": "^4.0.1",
|
||||
"compare-versions": "^3.6.0",
|
||||
"core-js": "^3.14.0",
|
||||
"core-js": "^3.15.2",
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "5.2.6",
|
||||
"electron": "13.1.4",
|
||||
"electron": "13.1.6",
|
||||
"electron-builder": "22.10.5",
|
||||
"electron-download": "^4.1.1",
|
||||
"electron-installer-snap": "^5.1.0",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"electron-rebuild": "^2.3.5",
|
||||
"eslint": "^7.29.0",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"file-loader": "^6.2.0",
|
||||
"graceful-fs": "^4.2.6",
|
||||
@@ -32,6 +40,7 @@
|
||||
"json-loader": "0.5.7",
|
||||
"lru-cache": "^6.0.0",
|
||||
"macos-release": "^2.5.0",
|
||||
"ngx-toastr": "^14.0.0",
|
||||
"node-abi": "^2.30.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"npmlog": "4.1.2",
|
||||
@@ -45,26 +54,31 @@
|
||||
"raw-loader": "4.0.2",
|
||||
"sass-loader": "^12.1.0",
|
||||
"shelljs": "0.8.4",
|
||||
"slugify": "^1.5.3",
|
||||
"source-code-pro": "^2.38.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-sans-pro": "3.6.0",
|
||||
"style-loader": "^3.0.0",
|
||||
"svg-inline-loader": "^0.8.2",
|
||||
"ts-loader": "^9.2.3",
|
||||
"tslib": "^2.3.0",
|
||||
"typedoc": "^0.21.2",
|
||||
"typescript": "^4.2.4",
|
||||
"typescript": "^4.3.5",
|
||||
"url-loader": "^4.1.1",
|
||||
"val-loader": "4.0.0",
|
||||
"webpack": "^5.41.0",
|
||||
"webpack": "^5.43.0",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"yaml-loader": "0.6.0"
|
||||
"yaml-loader": "0.6.0",
|
||||
"zone.js": "^0.11.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"lzma-native": "^8.0.0",
|
||||
"*/node-abi": "^2.30.0",
|
||||
"**/graceful-fs": "^4.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config tabby-core/webpack.config.js && webpack --color --config tabby-settings/webpack.config.js && webpack --color --config tabby-terminal/webpack.config.js && webpack --color --config tabby-local/webpack.config.js && webpack --color --config tabby-plugin-manager/webpack.config.js && webpack --color --config tabby-community-color-schemes/webpack.config.js && webpack --color --config tabby-ssh/webpack.config.js && webpack --color --config tabby-serial/webpack.config.js && webpack --color --config tabby-electron/webpack.config.js && webpack --color --config tabby-web/webpack.config.js && webpack --color --config web/webpack.config.js",
|
||||
"build": "npm run build:typings && node scripts/build-modules.js",
|
||||
"build:typings": "node scripts/build-typings.js",
|
||||
"watch": "cross-env TABBY_DEV=1 webpack --progress --color --watch",
|
||||
"start": "cross-env TABBY_DEV=1 electron app --debug --inspect",
|
||||
|
20
scripts/build-modules.js
Executable file
20
scripts/build-modules.js
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env node
|
||||
const sh = require('shelljs')
|
||||
const vars = require('./vars')
|
||||
const log = require('npmlog')
|
||||
const webpack = require('webpack')
|
||||
const { promisify } = require('util')
|
||||
|
||||
const configs = [
|
||||
'../app/webpack.main.config.js',
|
||||
'../app/webpack.config.js',
|
||||
'../web/webpack.config.js',
|
||||
...vars.builtinPlugins.map(x => `../${x}/webpack.config.js`),
|
||||
]
|
||||
|
||||
;(async () => {
|
||||
for (const c of configs) {
|
||||
log.info('build', c)
|
||||
await promisify(webpack)(require(c))
|
||||
}
|
||||
})()
|
12
scripts/generate-icon-metadata.js
Executable file
12
scripts/generate-icon-metadata.js
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
const jsYaml = require('js-yaml')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const metadata = jsYaml.load(fs.readFileSync(path.resolve(__dirname, '../node_modules/@fortawesome/fontawesome-free/metadata/icons.yml')))
|
||||
|
||||
let result = {}
|
||||
for (let key in metadata) {
|
||||
result[key] = metadata[key].styles.map(x => x[0])
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.resolve(__dirname, '../tabby-core/src/icons.json'), JSON.stringify(result))
|
@@ -3,7 +3,7 @@ const sh = require('shelljs')
|
||||
const vars = require('./vars')
|
||||
const log = require('npmlog')
|
||||
|
||||
vars.builtinPlugins.forEach(plugin => {
|
||||
;[...vars.builtinPlugins, 'web'].forEach(plugin => {
|
||||
log.info('bump', plugin)
|
||||
sh.cd(plugin)
|
||||
sh.exec('npm --no-git-tag-version version ' + vars.version)
|
||||
|
@@ -5,28 +5,29 @@ const childProcess = require('child_process')
|
||||
|
||||
const electronInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../node_modules/electron/package.json')))
|
||||
|
||||
exports.version = childProcess.execSync('git describe --tags', {encoding:'utf-8'})
|
||||
exports.version = childProcess.execSync('git describe --tags', { encoding:'utf-8' })
|
||||
exports.version = exports.version.substring(1).trim()
|
||||
exports.version = exports.version.replace('-', '-c')
|
||||
|
||||
if (exports.version.includes('-c')) {
|
||||
exports.version = semver.inc(exports.version, 'prepatch').replace('-0', '-nightly.0')
|
||||
exports.version = semver.inc(exports.version, 'prepatch').replace('-0', '-nightly.0')
|
||||
}
|
||||
|
||||
exports.builtinPlugins = [
|
||||
'tabby-core',
|
||||
'tabby-settings',
|
||||
'tabby-terminal',
|
||||
'tabby-electron',
|
||||
'tabby-local',
|
||||
'tabby-web',
|
||||
'tabby-community-color-schemes',
|
||||
'tabby-plugin-manager',
|
||||
'tabby-ssh',
|
||||
'tabby-serial',
|
||||
'tabby-core',
|
||||
'tabby-settings',
|
||||
'tabby-terminal',
|
||||
'tabby-electron',
|
||||
'tabby-local',
|
||||
'tabby-web',
|
||||
'tabby-community-color-schemes',
|
||||
'tabby-plugin-manager',
|
||||
'tabby-ssh',
|
||||
'tabby-serial',
|
||||
'tabby-telnet',
|
||||
]
|
||||
exports.bundledModules = [
|
||||
'@angular',
|
||||
'@ng-bootstrap',
|
||||
'@angular',
|
||||
'@ng-bootstrap',
|
||||
]
|
||||
exports.electronVersion = electronInfo.version
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-community-color-schemes",
|
||||
"version": "1.0.144",
|
||||
"version": "1.0.145",
|
||||
"description": "Community color schemes for Tabby",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-core",
|
||||
"version": "1.0.144",
|
||||
"version": "1.0.145",
|
||||
"description": "Tabby core",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
@@ -19,7 +19,6 @@
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.0",
|
||||
"bootstrap": "^4.1.3",
|
||||
"clone-deep": "^4.0.1",
|
||||
"core-js": "^3.1.2",
|
||||
"deep-equal": "^2.0.5",
|
||||
"deepmerge": "^4.1.1",
|
||||
|
@@ -2,7 +2,7 @@ export { BaseComponent, SubscriptionContainer } from '../components/base.compone
|
||||
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
||||
export { TabHeaderComponent } from '../components/tabHeader.component'
|
||||
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
|
||||
export { TabRecoveryProvider, RecoveredTab, RecoveryToken } from './tabRecovery'
|
||||
export { TabRecoveryProvider, RecoveryToken } from './tabRecovery'
|
||||
export { ToolbarButtonProvider, ToolbarButton } from './toolbarButtonProvider'
|
||||
export { ConfigProvider } from './configProvider'
|
||||
export { HotkeyProvider, HotkeyDescription } from './hotkeyProvider'
|
||||
@@ -16,6 +16,8 @@ export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
|
||||
export { HostWindowService } from './hostWindow'
|
||||
export { HostAppService, Platform } from './hostApp'
|
||||
export { FileProvider } from './fileProvider'
|
||||
export { ProfileProvider, Profile, ProfileSettingsComponent } from './profileProvider'
|
||||
export { PromptModalComponent } from '../components/promptModal.component'
|
||||
|
||||
export { AppService } from '../services/app.service'
|
||||
export { ConfigService } from '../services/config.service'
|
||||
@@ -25,8 +27,9 @@ export { HomeBaseService } from '../services/homeBase.service'
|
||||
export { HotkeysService } from '../services/hotkeys.service'
|
||||
export { NotificationsService } from '../services/notifications.service'
|
||||
export { ThemesService } from '../services/themes.service'
|
||||
export { ProfilesService } from '../services/profiles.service'
|
||||
export { SelectorService } from '../services/selector.service'
|
||||
export { TabsService } from '../services/tabs.service'
|
||||
export { TabsService, NewTabParameters, TabComponentType } from '../services/tabs.service'
|
||||
export { UpdaterService } from '../services/updater.service'
|
||||
export { VaultService, Vault, VaultSecret, VAULT_SECRET_TYPE_FILE } from '../services/vault.service'
|
||||
export { FileProvidersService } from '../services/fileProviders.service'
|
||||
|
44
tabby-core/src/api/profileProvider.ts
Normal file
44
tabby-core/src/api/profileProvider.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { NewTabParameters } from '../services/tabs.service'
|
||||
|
||||
export interface Profile {
|
||||
id?: string
|
||||
type: string
|
||||
name: string
|
||||
group?: string
|
||||
options?: Record<string, any>
|
||||
|
||||
icon?: string
|
||||
color?: string
|
||||
disableDynamicTitle?: boolean
|
||||
|
||||
weight?: number
|
||||
isBuiltin?: boolean
|
||||
isTemplate?: boolean
|
||||
}
|
||||
|
||||
export interface ProfileSettingsComponent {
|
||||
profile: Profile
|
||||
save?: () => void
|
||||
}
|
||||
|
||||
export abstract class ProfileProvider {
|
||||
id: string
|
||||
name: string
|
||||
supportsQuickConnect = false
|
||||
settingsComponent: new (...args: any[]) => ProfileSettingsComponent
|
||||
|
||||
abstract getBuiltinProfiles (): Promise<Profile[]>
|
||||
|
||||
abstract getNewTabParameters (profile: Profile): Promise<NewTabParameters<BaseTabComponent>>
|
||||
|
||||
abstract getDescription (profile: Profile): string
|
||||
|
||||
quickConnect (query: string): Profile|null {
|
||||
return null
|
||||
}
|
||||
|
||||
deleteProfile (profile: Profile): void { }
|
||||
}
|
@@ -1,17 +1,6 @@
|
||||
import deepClone from 'clone-deep'
|
||||
import { TabComponentType } from '../services/tabs.service'
|
||||
|
||||
export interface RecoveredTab {
|
||||
/**
|
||||
* Component type to be instantiated
|
||||
*/
|
||||
type: TabComponentType
|
||||
|
||||
/**
|
||||
* Component instance inputs
|
||||
*/
|
||||
options?: any
|
||||
}
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { NewTabParameters } from '../services/tabs.service'
|
||||
|
||||
export interface RecoveryToken {
|
||||
[_: string]: any
|
||||
@@ -35,19 +24,20 @@ export interface RecoveryToken {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export abstract class TabRecoveryProvider {
|
||||
export abstract class TabRecoveryProvider <T extends BaseTabComponent> {
|
||||
/**
|
||||
* @param recoveryToken a recovery token found in the saved tabs list
|
||||
* @returns [[boolean]] whether this [[TabRecoveryProvider]] can recover a tab from this token
|
||||
*/
|
||||
|
||||
abstract applicableTo (recoveryToken: RecoveryToken): Promise<boolean>
|
||||
|
||||
/**
|
||||
* @param recoveryToken a recovery token found in the saved tabs list
|
||||
* @returns [[RecoveredTab]] descriptor containing tab type and component inputs
|
||||
* @returns [[NewTabParameters]] descriptor containing tab type and component inputs
|
||||
* or `null` if this token is from a different tab type or is not supported
|
||||
*/
|
||||
abstract recover (recoveryToken: RecoveryToken): Promise<RecoveredTab>
|
||||
abstract recover (recoveryToken: RecoveryToken): Promise<NewTabParameters<T>>
|
||||
|
||||
/**
|
||||
* @param recoveryToken a recovery token found in the saved tabs list
|
||||
|
124
tabby-core/src/buttonProvider.ts
Normal file
124
tabby-core/src/buttonProvider.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Injectable } from '@angular/core'
|
||||
|
||||
import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider'
|
||||
import { SelectorService } from './services/selector.service'
|
||||
import { HostAppService, Platform } from './api/hostApp'
|
||||
import { Profile } from './api/profileProvider'
|
||||
import { ConfigService } from './services/config.service'
|
||||
import { SelectorOption } from './api/selector'
|
||||
import { HotkeysService } from './services/hotkeys.service'
|
||||
import { ProfilesService } from './services/profiles.service'
|
||||
import { AppService } from './services/app.service'
|
||||
import { NotificationsService } from './services/notifications.service'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class ButtonProvider extends ToolbarButtonProvider {
|
||||
constructor (
|
||||
private selector: SelectorService,
|
||||
private app: AppService,
|
||||
private hostApp: HostAppService,
|
||||
private profilesServices: ProfilesService,
|
||||
private config: ConfigService,
|
||||
private notifications: NotificationsService,
|
||||
hotkeys: HotkeysService,
|
||||
) {
|
||||
super()
|
||||
hotkeys.hotkey$.subscribe(hotkey => {
|
||||
if (hotkey === 'profile-selector') {
|
||||
this.activate()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async activate () {
|
||||
const recentProfiles: Profile[] = this.config.store.recentProfiles
|
||||
|
||||
const getProfileOptions = (profile): SelectorOption<void> => {
|
||||
const result: SelectorOption<void> = this.profilesServices.selectorOptionForProfile(profile)
|
||||
if (recentProfiles.includes(profile)) {
|
||||
result.icon = 'fas fa-history'
|
||||
}
|
||||
result.callback = () => this.launchProfile(profile)
|
||||
return result
|
||||
}
|
||||
|
||||
let options = recentProfiles.map(getProfileOptions)
|
||||
if (recentProfiles.length) {
|
||||
options.push({
|
||||
name: 'Clear recent connections',
|
||||
icon: 'fas fa-eraser',
|
||||
callback: () => {
|
||||
this.config.store.recentProfiles = []
|
||||
this.config.save()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
let profiles = await this.profilesServices.getProfiles()
|
||||
|
||||
if (!this.config.store.terminal.showBuiltinProfiles) {
|
||||
profiles = profiles.filter(x => !x.isBuiltin)
|
||||
}
|
||||
|
||||
profiles = profiles.filter(x => !x.isTemplate)
|
||||
|
||||
options = [...options, ...profiles.map(getProfileOptions)]
|
||||
|
||||
try {
|
||||
const { SettingsTabComponent } = window['nodeRequire']('tabby-settings')
|
||||
options.push({
|
||||
name: 'Manage profiles',
|
||||
icon: 'fas fa-window-restore',
|
||||
callback: () => this.app.openNewTabRaw({
|
||||
type: SettingsTabComponent,
|
||||
inputs: { activeTab: 'profiles' },
|
||||
}),
|
||||
})
|
||||
} catch { }
|
||||
|
||||
if (this.profilesServices.getProviders().some(x => x.supportsQuickConnect)) {
|
||||
options.push({
|
||||
name: 'Quick connect',
|
||||
freeInputPattern: 'Connect to "%s"...',
|
||||
icon: 'fas fa-arrow-right',
|
||||
callback: query => this.quickConnect(query),
|
||||
})
|
||||
}
|
||||
await this.selector.show('Select profile', options)
|
||||
}
|
||||
|
||||
quickConnect (query: string) {
|
||||
for (const provider of this.profilesServices.getProviders()) {
|
||||
const profile = provider.quickConnect(query)
|
||||
if (profile) {
|
||||
this.launchProfile(profile)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.notifications.error(`Could not parse "${query}"`)
|
||||
}
|
||||
|
||||
async launchProfile (profile: Profile) {
|
||||
await this.profilesServices.openNewTabForProfile(profile)
|
||||
|
||||
const recentProfiles = this.config.store.recentProfiles
|
||||
recentProfiles.unshift(profile)
|
||||
if (recentProfiles.length > 5) {
|
||||
recentProfiles.pop()
|
||||
}
|
||||
this.config.store.recentProfiles = recentProfiles
|
||||
this.config.save()
|
||||
}
|
||||
|
||||
provide (): ToolbarButton[] {
|
||||
return [{
|
||||
icon: this.hostApp.platform === Platform.Web
|
||||
? require('./icons/plus.svg')
|
||||
: require('./icons/profiles.svg'),
|
||||
title: 'New tab with profile',
|
||||
click: () => this.activate(),
|
||||
}]
|
||||
}
|
||||
}
|
@@ -1,6 +1,41 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HostAppService } from './api/hostApp'
|
||||
import { CLIHandler, CLIEvent } from './api/cli'
|
||||
import { HostWindowService } from './api/hostWindow'
|
||||
import { ProfilesService } from './services/profiles.service'
|
||||
|
||||
@Injectable()
|
||||
export class ProfileCLIHandler extends CLIHandler {
|
||||
firstMatchOnly = true
|
||||
priority = 0
|
||||
|
||||
constructor (
|
||||
private profiles: ProfilesService,
|
||||
private hostWindow: HostWindowService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async handle (event: CLIEvent): Promise<boolean> {
|
||||
const op = event.argv._[0]
|
||||
|
||||
if (op === 'profile') {
|
||||
this.handleOpenProfile(event.argv.profileName)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private async handleOpenProfile (profileName: string) {
|
||||
const profile = (await this.profiles.getProfiles()).find(x => x.name === profileName)
|
||||
if (!profile) {
|
||||
console.error('Requested profile', profileName, 'not found')
|
||||
return
|
||||
}
|
||||
this.profiles.openNewTabForProfile(profile)
|
||||
this.hostWindow.bringToFront()
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class LastCLIHandler extends CLIHandler {
|
||||
|
@@ -80,7 +80,7 @@ export class AppRootComponent {
|
||||
this.logger = log.create('main')
|
||||
this.logger.info('v', platform.getAppVersion())
|
||||
|
||||
this.hotkeys.matchedHotkey.subscribe((hotkey: string) => {
|
||||
this.hotkeys.hotkey$.subscribe((hotkey: string) => {
|
||||
if (hotkey.startsWith('tab-')) {
|
||||
const index = parseInt(hotkey.split('-')[1])
|
||||
if (index <= this.app.tabs.length) {
|
||||
|
@@ -15,7 +15,7 @@
|
||||
*ngFor='let option of filteredOptions; let i = index'
|
||||
)
|
||||
i.icon(
|
||||
class='fa-fw fas fa-{{option.icon}}',
|
||||
class='fa-fw {{option.icon}}',
|
||||
*ngIf='!iconIsSVG(option.icon)'
|
||||
)
|
||||
.icon(
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, AfterViewInit, OnDestroy } from '@angular/core'
|
||||
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
||||
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from '../api/tabRecovery'
|
||||
import { TabsService } from '../services/tabs.service'
|
||||
import { TabRecoveryProvider, RecoveryToken } from '../api/tabRecovery'
|
||||
import { TabsService, NewTabParameters } from '../services/tabs.service'
|
||||
import { HotkeysService } from '../services/hotkeys.service'
|
||||
import { TabRecoveryService } from '../services/tabRecovery.service'
|
||||
|
||||
@@ -209,7 +209,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
})
|
||||
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
||||
|
||||
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, hotkey => {
|
||||
this.subscribeUntilDestroyed(this.hotkeys.hotkey$, hotkey => {
|
||||
if (!this.hasFocus || !this.focusedTab) {
|
||||
return
|
||||
}
|
||||
@@ -453,7 +453,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
async splitTab (tab: BaseTabComponent, dir: SplitDirection): Promise<BaseTabComponent|null> {
|
||||
const newTab = await this.tabsService.duplicate(tab)
|
||||
if (newTab) {
|
||||
this.addTab(newTab, tab, dir)
|
||||
await this.addTab(newTab, tab, dir)
|
||||
}
|
||||
return newTab
|
||||
}
|
||||
@@ -601,7 +601,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
} else {
|
||||
const recovered = await this.tabRecovery.recoverTab(childState, duplicate)
|
||||
if (recovered) {
|
||||
const tab = this.tabsService.create(recovered.type, recovered.options)
|
||||
const tab = this.tabsService.create(recovered)
|
||||
children.push(tab)
|
||||
tab.parent = this
|
||||
this.attachTabView(tab)
|
||||
@@ -619,15 +619,15 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider {
|
||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabComponent> {
|
||||
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
||||
return recoveryToken.type === 'app:split-tab'
|
||||
}
|
||||
|
||||
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab> {
|
||||
async recover (recoveryToken: RecoveryToken): Promise<NewTabParameters<SplitTabComponent>> {
|
||||
return {
|
||||
type: SplitTabComponent,
|
||||
options: { _recoveredState: recoveryToken },
|
||||
inputs: { _recoveredState: recoveryToken },
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,20 +19,18 @@ import { BaseTabComponent } from '../components/baseTab.component'
|
||||
export class TabBodyComponent implements OnChanges {
|
||||
@Input() @HostBinding('class.active') active: boolean
|
||||
@Input() tab: BaseTabComponent
|
||||
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
|
||||
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder?: ViewContainerRef
|
||||
|
||||
ngOnChanges (changes) {
|
||||
if (changes.tab) {
|
||||
if (this.placeholder) {
|
||||
this.placeholder.detach()
|
||||
}
|
||||
this.placeholder?.detach()
|
||||
setImmediate(() => {
|
||||
this.placeholder.insert(this.tab.hostView)
|
||||
this.placeholder?.insert(this.tab.hostView)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.placeholder.detach()
|
||||
this.placeholder?.detach()
|
||||
}
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ export class TabHeaderComponent extends BaseComponent {
|
||||
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
|
||||
) {
|
||||
super()
|
||||
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, (hotkey) => {
|
||||
this.subscribeUntilDestroyed(this.hotkeys.hotkey$, (hotkey) => {
|
||||
if (this.app.activeTab === this.tab) {
|
||||
if (hotkey === 'rename-tab') {
|
||||
this.showRenameTabModal()
|
||||
|
@@ -19,18 +19,6 @@
|
||||
.description Toggles the Tabby window visibility
|
||||
toggle([(ngModel)]='enableGlobalHotkey')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Enable #[strong SSH] plugin
|
||||
.description Adds an SSH connection manager UI to Tabby
|
||||
toggle([(ngModel)]='enableSSH')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Enable #[strong Serial] plugin
|
||||
.description Allows attaching Tabby to serial ports
|
||||
toggle([(ngModel)]='enableSerial')
|
||||
|
||||
|
||||
.text-center.mt-5
|
||||
button.btn.btn-primary((click)='closeAndDisable()') Close and never show again
|
||||
|
@@ -11,8 +11,6 @@ import { HostWindowService } from '../api/hostWindow'
|
||||
styles: [require('./welcomeTab.component.scss')],
|
||||
})
|
||||
export class WelcomeTabComponent extends BaseTabComponent {
|
||||
enableSSH = false
|
||||
enableSerial = false
|
||||
enableGlobalHotkey = true
|
||||
|
||||
constructor (
|
||||
@@ -21,23 +19,15 @@ export class WelcomeTabComponent extends BaseTabComponent {
|
||||
) {
|
||||
super()
|
||||
this.setTitle('Welcome')
|
||||
this.enableSSH = !config.store.pluginBlacklist.includes('ssh')
|
||||
this.enableSerial = !config.store.pluginBlacklist.includes('serial')
|
||||
}
|
||||
|
||||
closeAndDisable () {
|
||||
async closeAndDisable () {
|
||||
this.config.store.enableWelcomeTab = false
|
||||
this.config.store.pluginBlacklist = []
|
||||
if (!this.enableSSH) {
|
||||
this.config.store.pluginBlacklist.push('ssh')
|
||||
}
|
||||
if (!this.enableSerial) {
|
||||
this.config.store.pluginBlacklist.push('serial')
|
||||
}
|
||||
if (!this.enableGlobalHotkey) {
|
||||
this.config.store.hotkeys['toggle-window'] = []
|
||||
}
|
||||
this.config.save()
|
||||
await this.config.save()
|
||||
this.hostWindow.reload()
|
||||
}
|
||||
}
|
||||
|
@@ -69,4 +69,6 @@ hotkeys:
|
||||
pane-maximize:
|
||||
- 'Ctrl-Alt-Enter'
|
||||
close-pane: []
|
||||
profile-selector:
|
||||
- 'Ctrl-Shift-T'
|
||||
pluginBlacklist: ['ssh']
|
||||
|
@@ -68,4 +68,6 @@ hotkeys:
|
||||
- '⌘-⌥-Enter'
|
||||
close-pane:
|
||||
- '⌘-Shift-W'
|
||||
profile-selector:
|
||||
- '⌘-E'
|
||||
pluginBlacklist: ['ssh']
|
||||
|
@@ -70,4 +70,6 @@ hotkeys:
|
||||
pane-maximize:
|
||||
- 'Ctrl-Alt-Enter'
|
||||
close-pane: []
|
||||
profile-selector:
|
||||
- 'Ctrl-Shift-T'
|
||||
pluginBlacklist: []
|
||||
|
@@ -15,7 +15,13 @@ appearance:
|
||||
vibrancy: true
|
||||
vibrancyType: 'blur'
|
||||
terminal:
|
||||
recoverTabs: true
|
||||
showBuiltinProfiles: true
|
||||
hotkeys:
|
||||
profile:
|
||||
__nonStructural: true
|
||||
profiles: []
|
||||
recentProfiles: []
|
||||
recoverTabs: true
|
||||
enableAnalytics: true
|
||||
enableWelcomeTab: true
|
||||
electronFlags:
|
||||
|
@@ -0,0 +1,19 @@
|
||||
import { Directive, ElementRef, AfterViewInit } from '@angular/core'
|
||||
|
||||
/** @hidden */
|
||||
@Directive({
|
||||
selector: '[alwaysVisibleTypeahead]',
|
||||
})
|
||||
export class AlwaysVisibleTypeaheadDirective implements AfterViewInit {
|
||||
constructor (private el: ElementRef) { }
|
||||
|
||||
ngAfterViewInit (): void {
|
||||
this.el.nativeElement.addEventListener('focus', e => {
|
||||
e.stopPropagation()
|
||||
setTimeout(() => {
|
||||
const inputEvent: Event = new Event('input')
|
||||
e.target.dispatchEvent(inputEvent)
|
||||
}, 0)
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,10 +1,15 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ProfilesService } from './services/profiles.service'
|
||||
import { HotkeyDescription, HotkeyProvider } from './api/hotkeyProvider'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class AppHotkeyProvider extends HotkeyProvider {
|
||||
hotkeys: HotkeyDescription[] = [
|
||||
{
|
||||
id: 'profile-selector',
|
||||
name: 'Show profile selector',
|
||||
},
|
||||
{
|
||||
id: 'toggle-fullscreen',
|
||||
name: 'Toggle fullscreen mode',
|
||||
@@ -171,7 +176,18 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
||||
},
|
||||
]
|
||||
|
||||
constructor (
|
||||
private profilesService: ProfilesService,
|
||||
) { super() }
|
||||
|
||||
async provide (): Promise<HotkeyDescription[]> {
|
||||
return this.hotkeys
|
||||
const profiles = await this.profilesService.getProfiles()
|
||||
return [
|
||||
...this.hotkeys,
|
||||
...profiles.map(profile => ({
|
||||
id: `profile.${profile.id}`,
|
||||
name: `New tab: ${profile.name}`,
|
||||
})),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
1
tabby-core/src/icons.json
Normal file
1
tabby-core/src/icons.json
Normal file
File diff suppressed because one or more lines are too long
1
tabby-core/src/icons/plus.svg
Normal file
1
tabby-core/src/icons/plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="svg-inline--fa fa-plus fa-w-12 fa-3x" data-icon="plus" data-prefix="fal" focusable="false" role="img" viewBox="0 0 384 512"><path fill="#fff" stroke="none" stroke-width="1" d="M376 232H216V72c0-4.42-3.58-8-8-8h-32c-4.42 0-8 3.58-8 8v160H8c-4.42 0-8 3.58-8 8v32c0 4.42 3.58 8 8 8h160v160c0 4.42 3.58 8 8 8h32c4.42 0 8-3.58 8-8V280h160c4.42 0 8-3.58 8-8v-32c0-4.42-3.58-8-8-8z"/></svg>
|
After Width: | Height: | Size: 449 B |
Before Width: | Height: | Size: 665 B After Width: | Height: | Size: 665 B |
@@ -10,6 +10,7 @@ import { DndModule } from 'ng2-dnd'
|
||||
import { AppRootComponent } from './components/appRoot.component'
|
||||
import { CheckboxComponent } from './components/checkbox.component'
|
||||
import { TabBodyComponent } from './components/tabBody.component'
|
||||
import { PromptModalComponent } from './components/promptModal.component'
|
||||
import { SafeModeModalComponent } from './components/safeModeModal.component'
|
||||
import { StartPageComponent } from './components/startPage.component'
|
||||
import { TabHeaderComponent } from './components/tabHeader.component'
|
||||
@@ -25,20 +26,23 @@ import { WelcomeTabComponent } from './components/welcomeTab.component'
|
||||
import { TransfersMenuComponent } from './components/transfersMenu.component'
|
||||
|
||||
import { AutofocusDirective } from './directives/autofocus.directive'
|
||||
import { AlwaysVisibleTypeaheadDirective } from './directives/alwaysVisibleTypeahead.directive'
|
||||
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
|
||||
import { DropZoneDirective } from './directives/dropZone.directive'
|
||||
|
||||
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider } from './api'
|
||||
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ToolbarButtonProvider, ProfilesService } from './api'
|
||||
|
||||
import { AppService } from './services/app.service'
|
||||
import { ConfigService } from './services/config.service'
|
||||
import { VaultFileProvider } from './services/vault.service'
|
||||
import { HotkeysService } from './services/hotkeys.service'
|
||||
|
||||
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
||||
import { CoreConfigProvider } from './config'
|
||||
import { AppHotkeyProvider } from './hotkeys'
|
||||
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu } from './tabContextMenu'
|
||||
import { LastCLIHandler } from './cli'
|
||||
import { LastCLIHandler, ProfileCLIHandler } from './cli'
|
||||
import { ButtonProvider } from './buttonProvider'
|
||||
|
||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||
import 'ng2-dnd/bundles/style.css'
|
||||
@@ -53,9 +57,11 @@ const PROVIDERS = [
|
||||
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
||||
{ provide: CLIHandler, useClass: ProfileCLIHandler, multi: true },
|
||||
{ provide: CLIHandler, useClass: LastCLIHandler, multi: true },
|
||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
||||
{ provide: FileProvider, useClass: VaultFileProvider, multi: true },
|
||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||
]
|
||||
|
||||
/** @hidden */
|
||||
@@ -72,6 +78,7 @@ const PROVIDERS = [
|
||||
declarations: [
|
||||
AppRootComponent as any,
|
||||
CheckboxComponent,
|
||||
PromptModalComponent,
|
||||
StartPageComponent,
|
||||
TabBodyComponent,
|
||||
TabHeaderComponent,
|
||||
@@ -82,6 +89,7 @@ const PROVIDERS = [
|
||||
SafeModeModalComponent,
|
||||
AutofocusDirective,
|
||||
FastHtmlBindDirective,
|
||||
AlwaysVisibleTypeaheadDirective,
|
||||
SelectorModalComponent,
|
||||
SplitTabComponent,
|
||||
SplitTabSpannerComponent,
|
||||
@@ -91,6 +99,7 @@ const PROVIDERS = [
|
||||
DropZoneDirective,
|
||||
],
|
||||
entryComponents: [
|
||||
PromptModalComponent,
|
||||
RenameTabModalComponent,
|
||||
SafeModeModalComponent,
|
||||
SelectorModalComponent,
|
||||
@@ -101,21 +110,40 @@ const PROVIDERS = [
|
||||
exports: [
|
||||
CheckboxComponent,
|
||||
ToggleComponent,
|
||||
PromptModalComponent,
|
||||
AutofocusDirective,
|
||||
DropZoneDirective,
|
||||
FastHtmlBindDirective,
|
||||
AlwaysVisibleTypeaheadDirective,
|
||||
],
|
||||
})
|
||||
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||
constructor (app: AppService, config: ConfigService, platform: PlatformService) {
|
||||
constructor (
|
||||
app: AppService,
|
||||
config: ConfigService,
|
||||
platform: PlatformService,
|
||||
hotkeys: HotkeysService,
|
||||
profilesService: ProfilesService,
|
||||
) {
|
||||
app.ready$.subscribe(() => {
|
||||
if (config.store.enableWelcomeTab) {
|
||||
app.openNewTabRaw(WelcomeTabComponent)
|
||||
app.openNewTabRaw({ type: WelcomeTabComponent })
|
||||
}
|
||||
})
|
||||
|
||||
platform.setErrorHandler(err => {
|
||||
console.error('Unhandled exception:', err)
|
||||
})
|
||||
|
||||
hotkeys.hotkey$.subscribe(async (hotkey) => {
|
||||
if (hotkey.startsWith('profile.')) {
|
||||
const id = hotkey.split('.')[1]
|
||||
const profile = (await profilesService.getProfiles()).find(x => x.id === id)
|
||||
if (profile) {
|
||||
profilesService.openNewTabForProfile(profile)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static forRoot (): ModuleWithProviders<AppModule> {
|
||||
|
@@ -1,6 +1,4 @@
|
||||
|
||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||
import { takeUntil } from 'rxjs/operators'
|
||||
import { Observable, Subject, AsyncSubject, takeUntil } from 'rxjs'
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
@@ -13,7 +11,7 @@ import { HostAppService } from '../api/hostApp'
|
||||
|
||||
import { ConfigService } from './config.service'
|
||||
import { TabRecoveryService } from './tabRecovery.service'
|
||||
import { TabsService, TabComponentType } from './tabs.service'
|
||||
import { TabsService, NewTabParameters } from './tabs.service'
|
||||
import { SelectorService } from './selector.service'
|
||||
|
||||
class CompletionObserver {
|
||||
@@ -88,10 +86,10 @@ export class AppService {
|
||||
|
||||
config.ready$.toPromise().then(async () => {
|
||||
if (this.bootstrapData.isFirstWindow) {
|
||||
if (config.store.terminal.recoverTabs) {
|
||||
if (config.store.recoverTabs) {
|
||||
const tabs = await this.tabRecovery.recoverTabs()
|
||||
for (const tab of tabs) {
|
||||
this.openNewTabRaw(tab.type, tab.options)
|
||||
this.openNewTabRaw(tab)
|
||||
}
|
||||
}
|
||||
/** Continue to store the tabs even if the setting is currently off */
|
||||
@@ -152,8 +150,8 @@ export class AppService {
|
||||
* Adds a new tab **without** wrapping it in a SplitTabComponent
|
||||
* @param inputs Properties to be assigned on the new tab component instance
|
||||
*/
|
||||
openNewTabRaw (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||
const tab = this.tabsService.create(type, inputs)
|
||||
openNewTabRaw <T extends BaseTabComponent> (params: NewTabParameters<T>): T {
|
||||
const tab = this.tabsService.create(params)
|
||||
this.addTabRaw(tab)
|
||||
return tab
|
||||
}
|
||||
@@ -162,9 +160,9 @@ export class AppService {
|
||||
* Adds a new tab while wrapping it in a SplitTabComponent
|
||||
* @param inputs Properties to be assigned on the new tab component instance
|
||||
*/
|
||||
openNewTab (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||
const splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
|
||||
const tab = this.tabsService.create(type, inputs)
|
||||
openNewTab <T extends BaseTabComponent> (params: NewTabParameters<T>): T {
|
||||
const splitTab = this.tabsService.create({ type: SplitTabComponent })
|
||||
const tab = this.tabsService.create(params)
|
||||
splitTab.addTab(tab, null, 'r')
|
||||
this.addTabRaw(splitTab)
|
||||
return tab
|
||||
@@ -175,7 +173,7 @@ export class AppService {
|
||||
if (token) {
|
||||
const recoveredTab = await this.tabRecovery.recoverTab(token)
|
||||
if (recoveredTab) {
|
||||
const tab = this.tabsService.create(recoveredTab.type, recoveredTab.options)
|
||||
const tab = this.tabsService.create(recoveredTab)
|
||||
if (this.activeTab) {
|
||||
this.addTabRaw(tab, this.tabs.indexOf(this.activeTab) + 1)
|
||||
} else {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as yaml from 'js-yaml'
|
||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { ConfigProvider } from '../api/configProvider'
|
||||
import { PlatformService } from '../api/platform'
|
||||
@@ -58,18 +59,27 @@ export class ConfigProxy {
|
||||
if (real[key] !== undefined) {
|
||||
return real[key]
|
||||
} else {
|
||||
if (isNonStructuralObjectMember(defaults[key])) {
|
||||
real[key] = { ...defaults[key] }
|
||||
delete real[key].__nonStructural
|
||||
return real[key]
|
||||
} else {
|
||||
return defaults[key]
|
||||
}
|
||||
return this.getDefault(key)
|
||||
}
|
||||
}
|
||||
|
||||
this.getDefault = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
|
||||
if (isNonStructuralObjectMember(defaults[key])) {
|
||||
real[key] = { ...defaults[key] }
|
||||
delete real[key].__nonStructural
|
||||
return real[key]
|
||||
} else {
|
||||
return defaults[key]
|
||||
}
|
||||
}
|
||||
|
||||
this.setValue = (key: string, value: any) => { // eslint-disable-line @typescript-eslint/unbound-method
|
||||
real[key] = value
|
||||
if (value === this.getDefault(key)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete real[key]
|
||||
} else {
|
||||
real[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +87,8 @@ export class ConfigProxy {
|
||||
getValue (_key: string): any { }
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||
setValue (_key: string, _value: any) { }
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||
getDefault (_key: string): any { }
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -183,10 +195,10 @@ export class ConfigService {
|
||||
/**
|
||||
* Writes config YAML as string
|
||||
*/
|
||||
writeRaw (data: string): void {
|
||||
async writeRaw (data: string): Promise<void> {
|
||||
this._store = yaml.load(data)
|
||||
this.save()
|
||||
this.load()
|
||||
await this.save()
|
||||
await this.load()
|
||||
this.emitChange()
|
||||
}
|
||||
|
||||
@@ -228,8 +240,8 @@ export class ConfigService {
|
||||
this.ready.next(true)
|
||||
this.ready.complete()
|
||||
|
||||
this.hostApp.configChangeBroadcast$.subscribe(() => {
|
||||
this.load()
|
||||
this.hostApp.configChangeBroadcast$.subscribe(async () => {
|
||||
await this.load()
|
||||
this.emitChange()
|
||||
})
|
||||
}
|
||||
@@ -250,6 +262,67 @@ export class ConfigService {
|
||||
}
|
||||
config.version = 1
|
||||
}
|
||||
if (config.version < 2) {
|
||||
config.profiles ??= []
|
||||
if (config.terminal?.recoverTabs !== undefined) {
|
||||
config.recoverTabs = config.terminal.recoverTabs
|
||||
delete config.terminal.recoverTabs
|
||||
}
|
||||
for (const profile of config.terminal?.profiles ?? []) {
|
||||
if (profile.sessionOptions) {
|
||||
profile.options = profile.sessionOptions
|
||||
delete profile.sessionOptions
|
||||
}
|
||||
profile.type = 'local'
|
||||
profile.id = `local:custom:${uuidv4()}`
|
||||
}
|
||||
if (config.terminal?.profiles) {
|
||||
config.profiles = config.terminal.profiles
|
||||
delete config.terminal.profiles
|
||||
delete config.terminal.environment
|
||||
config.terminal.profile = `local:${config.terminal.profile}`
|
||||
}
|
||||
config.version = 2
|
||||
}
|
||||
if (config.version < 3) {
|
||||
delete config.ssh?.recentConnections
|
||||
for (const c of config.ssh?.connections ?? []) {
|
||||
const p = {
|
||||
id: `ssh:${uuidv4()}`,
|
||||
type: 'ssh',
|
||||
icon: 'fas fa-desktop',
|
||||
name: c.name,
|
||||
group: c.group ?? undefined,
|
||||
color: c.color,
|
||||
disableDynamicTitle: c.disableDynamicTitle,
|
||||
options: c,
|
||||
}
|
||||
config.profiles.push(p)
|
||||
}
|
||||
for (const p of config.profiles ?? []) {
|
||||
if (p.type === 'ssh') {
|
||||
if (p.options.jumpHost) {
|
||||
p.options.jumpHost = config.profiles.find(x => x.name === p.options.jumpHost)?.id
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const c of config.serial?.connections ?? []) {
|
||||
const p = {
|
||||
id: `serial:${uuidv4()}`,
|
||||
type: 'serial',
|
||||
icon: 'fas fa-microchip',
|
||||
name: c.name,
|
||||
group: c.group ?? undefined,
|
||||
color: c.color,
|
||||
options: c,
|
||||
}
|
||||
config.profiles.push(p)
|
||||
}
|
||||
delete config.ssh?.connections
|
||||
delete config.serial?.connections
|
||||
delete window.localStorage.lastSerialConnection
|
||||
config.version = 3
|
||||
}
|
||||
}
|
||||
|
||||
private async maybeDecryptConfig (store) {
|
||||
|
@@ -3,6 +3,7 @@ import { Observable, Subject } from 'rxjs'
|
||||
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
||||
import { stringifyKeySequence, EventData } from './hotkeys.util'
|
||||
import { ConfigService } from './config.service'
|
||||
import { deprecate } from 'util'
|
||||
|
||||
export interface PartialHotkeyMatch {
|
||||
id: string
|
||||
@@ -45,14 +46,14 @@ export class HotkeysService {
|
||||
}
|
||||
})
|
||||
})
|
||||
this.config.ready$.toPromise().then(() => {
|
||||
this.getHotkeyDescriptions().then(hotkeys => {
|
||||
this.hotkeyDescriptions = hotkeys
|
||||
})
|
||||
this.config.ready$.toPromise().then(async () => {
|
||||
const hotkeys = await this.getHotkeyDescriptions()
|
||||
this.hotkeyDescriptions = hotkeys
|
||||
})
|
||||
|
||||
// deprecated
|
||||
this.hotkey$.subscribe(h => this.matchedHotkey.emit(h))
|
||||
this.matchedHotkey.subscribe = deprecate(s => this.hotkey$.subscribe(s), 'matchedHotkey is deprecated, use hotkey$')
|
||||
}
|
||||
|
||||
/**
|
||||
|
63
tabby-core/src/services/profiles.service.ts
Normal file
63
tabby-core/src/services/profiles.service.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { NewTabParameters } from './tabs.service'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { Profile, ProfileProvider } from '../api/profileProvider'
|
||||
import { SelectorOption } from '../api/selector'
|
||||
import { AppService } from './app.service'
|
||||
import { ConfigService } from './config.service'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProfilesService {
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private config: ConfigService,
|
||||
@Inject(ProfileProvider) private profileProviders: ProfileProvider[],
|
||||
) { }
|
||||
|
||||
async openNewTabForProfile (profile: Profile): Promise<BaseTabComponent|null> {
|
||||
const params = await this.newTabParametersForProfile(profile)
|
||||
if (params) {
|
||||
const tab = this.app.openNewTab(params)
|
||||
;(this.app.getParentTab(tab) ?? tab).color = profile.color ?? null
|
||||
tab.setTitle(profile.name)
|
||||
if (profile.disableDynamicTitle) {
|
||||
tab['enableDynamicTitle'] = false
|
||||
}
|
||||
return tab
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async newTabParametersForProfile (profile: Profile): Promise<NewTabParameters<BaseTabComponent>|null> {
|
||||
return this.providerForProfile(profile)?.getNewTabParameters(profile) ?? null
|
||||
}
|
||||
|
||||
getProviders (): ProfileProvider[] {
|
||||
return [...this.profileProviders]
|
||||
}
|
||||
|
||||
async getProfiles (): Promise<Profile[]> {
|
||||
const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles()))
|
||||
let list = lists.reduce((a, b) => a.concat(b), [])
|
||||
list = [
|
||||
...this.config.store.profiles ?? [],
|
||||
...list,
|
||||
]
|
||||
const sortKey = p => `${p.group ?? ''} / ${p.name}`
|
||||
list.sort((a, b) => sortKey(a).localeCompare(sortKey(b)))
|
||||
list.sort((a, b) => (a.isBuiltin ? 1 : 0) - (b.isBuiltin ? 1 : 0))
|
||||
return list
|
||||
}
|
||||
|
||||
providerForProfile (profile: Profile): ProfileProvider|null {
|
||||
return this.profileProviders.find(x => x.id === profile.type) ?? null
|
||||
}
|
||||
|
||||
selectorOptionForProfile <T> (profile: Profile): SelectorOption<T> {
|
||||
return {
|
||||
icon: profile.icon,
|
||||
name: profile.group ? `${profile.group} / ${profile.name}` : profile.name,
|
||||
description: this.providerForProfile(profile)?.getDescription(profile),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from '../api/tabRecovery'
|
||||
import { TabRecoveryProvider, RecoveryToken } from '../api/tabRecovery'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { Logger, LogService } from '../services/log.service'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
import { Logger, LogService } from './log.service'
|
||||
import { ConfigService } from './config.service'
|
||||
import { NewTabParameters } from './tabs.service'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -11,7 +12,7 @@ export class TabRecoveryService {
|
||||
enabled = false
|
||||
|
||||
private constructor (
|
||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[]|null,
|
||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider<BaseTabComponent>[]|null,
|
||||
private config: ConfigService,
|
||||
log: LogService
|
||||
) {
|
||||
@@ -40,7 +41,7 @@ export class TabRecoveryService {
|
||||
return token
|
||||
}
|
||||
|
||||
async recoverTab (token: RecoveryToken, duplicate = false): Promise<RecoveredTab|null> {
|
||||
async recoverTab (token: RecoveryToken, duplicate = false): Promise<NewTabParameters<BaseTabComponent>|null> {
|
||||
for (const provider of this.config.enabledServices(this.tabRecoveryProviders ?? [])) {
|
||||
try {
|
||||
if (!await provider.applicableTo(token)) {
|
||||
@@ -50,9 +51,9 @@ export class TabRecoveryService {
|
||||
token = provider.duplicate(token)
|
||||
}
|
||||
const tab = await provider.recover(token)
|
||||
tab.options = tab.options || {}
|
||||
tab.options.color = token.tabColor ?? null
|
||||
tab.options.title = token.tabTitle || ''
|
||||
tab.inputs = tab.inputs ?? {}
|
||||
tab.inputs.color = token.tabColor ?? null
|
||||
tab.inputs.title = token.tabTitle || ''
|
||||
return tab
|
||||
} catch (error) {
|
||||
this.logger.warn('Tab recovery crashed:', token, provider, error)
|
||||
@@ -61,9 +62,9 @@ export class TabRecoveryService {
|
||||
return null
|
||||
}
|
||||
|
||||
async recoverTabs (): Promise<RecoveredTab[]> {
|
||||
async recoverTabs (): Promise<NewTabParameters<BaseTabComponent>[]> {
|
||||
if (window.localStorage.tabsRecovery) {
|
||||
const tabs: RecoveredTab[] = []
|
||||
const tabs: NewTabParameters<BaseTabComponent>[] = []
|
||||
for (const token of JSON.parse(window.localStorage.tabsRecovery)) {
|
||||
const tab = await this.recoverTab(token)
|
||||
if (tab) {
|
||||
|
@@ -3,7 +3,22 @@ import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { TabRecoveryService } from './tabRecovery.service'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-type-alias
|
||||
export type TabComponentType = new (...args: any[]) => BaseTabComponent
|
||||
export interface TabComponentType<T extends BaseTabComponent> {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
||||
new (...args: any[]): T
|
||||
}
|
||||
|
||||
export interface NewTabParameters<T extends BaseTabComponent> {
|
||||
/**
|
||||
* Component type to be instantiated
|
||||
*/
|
||||
type: TabComponentType<T>
|
||||
|
||||
/**
|
||||
* Component instance inputs
|
||||
*/
|
||||
inputs?: Record<string, any>
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TabsService {
|
||||
@@ -17,12 +32,12 @@ export class TabsService {
|
||||
/**
|
||||
* Instantiates a tab component and assigns given inputs
|
||||
*/
|
||||
create (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
||||
create <T extends BaseTabComponent> (params: NewTabParameters<T>): T {
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(params.type)
|
||||
const componentRef = componentFactory.create(this.injector)
|
||||
const tab = componentRef.instance
|
||||
tab.hostView = componentRef.hostView
|
||||
Object.assign(tab, inputs ?? {})
|
||||
Object.assign(tab, params.inputs ?? {})
|
||||
return tab
|
||||
}
|
||||
|
||||
@@ -36,7 +51,7 @@ export class TabsService {
|
||||
}
|
||||
const dup = await this.tabRecovery.recoverTab(token, true)
|
||||
if (dup) {
|
||||
return this.create(dup.type, dup.options)
|
||||
return this.create(dup)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
@@ -247,12 +247,12 @@ export class VaultFileProvider extends FileProvider {
|
||||
const result = await this.selector.show<VaultSecret|null>('Select file', [
|
||||
{
|
||||
name: 'Add a new file',
|
||||
icon: 'plus',
|
||||
icon: 'fas fa-plus',
|
||||
result: null,
|
||||
},
|
||||
...files.map(f => ({
|
||||
name: f.key.description,
|
||||
icon: 'file',
|
||||
icon: 'fas fa-file',
|
||||
result: f,
|
||||
})),
|
||||
])
|
||||
@@ -270,7 +270,7 @@ export class VaultFileProvider extends FileProvider {
|
||||
}
|
||||
const transfer = transfers[0]
|
||||
const id = (await wrapPromise(this.zone, promisify(crypto.randomBytes)(32))).toString('hex')
|
||||
this.vault.addSecret({
|
||||
await this.vault.addSecret({
|
||||
type: VAULT_SECRET_TYPE_FILE,
|
||||
key: {
|
||||
id,
|
||||
|
@@ -132,6 +132,10 @@ app-root {
|
||||
|
||||
tab-body {
|
||||
background: $content-bg;
|
||||
|
||||
.terminal-toolbar .btn, .toolbar-pin-button {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
multi-hotkey-input {
|
||||
@@ -235,12 +239,11 @@ hotkey-input-modal {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.list-group-light {
|
||||
.list-group-item {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, .1);
|
||||
border-top: 1px solid rgba(255, 255, 255, .05);
|
||||
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
|
@@ -12,7 +12,7 @@ export function isWindowsBuild (build: number): boolean {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getCSSFontFamily (config: any): string {
|
||||
let fonts: string[] = config.terminal.font.split(',').map(x => x.trim().replace(/"/g, ''))
|
||||
let fonts: string[] = config.terminal.font.split(',').map(x => x.trim().replaceAll('"', ''))
|
||||
if (config.terminal.fallbackFont) {
|
||||
fonts.push(config.terminal.fallbackFont)
|
||||
}
|
||||
@@ -31,3 +31,26 @@ export function wrapPromise <T> (zone: NgZone, promise: Promise<T>): Promise<T>
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export class ResettableTimeout {
|
||||
private fn: () => void
|
||||
private timeout: number
|
||||
private id: any
|
||||
|
||||
constructor (fn: () => void, timeout: number) {
|
||||
this.fn = fn
|
||||
this.timeout = timeout
|
||||
this.id = null
|
||||
}
|
||||
|
||||
set (timeout?: number): void {
|
||||
this.clear()
|
||||
this.id = setTimeout(this.fn, timeout ?? this.timeout)
|
||||
}
|
||||
|
||||
clear (): void {
|
||||
if (this.id) {
|
||||
clearTimeout(this.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,9 +3,9 @@
|
||||
|
||||
|
||||
"@types/js-yaml@^4.0.0":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.1.tgz#5544730b65a480b18ace6b6ce914e519cec2d43b"
|
||||
integrity sha512-xdOvNmXmrZqqPy3kuCQ+fz6wA0xU5pji9cd1nDrflWaAWtYLLGk5ykW0H6yg5TVyehHP1pfmuuSaZkhP+kspVA==
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.2.tgz#4117a7a378593a218e9d6f0ef44ce6d5d9edf7fa"
|
||||
integrity sha512-KbeHS/Y4R+k+5sWXEYzAZKuB1yQlZtEghuhRxrVRLaqhtoG5+26JwQsa4HyS3AWX8v1Uwukma5HheduUDskasA==
|
||||
|
||||
"@types/semver@^7.3.5":
|
||||
version "7.3.5"
|
||||
@@ -50,19 +50,10 @@ call-bind@^1.0.0, call-bind@^1.0.2:
|
||||
function-bind "^1.1.1"
|
||||
get-intrinsic "^1.0.2"
|
||||
|
||||
clone-deep@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
||||
integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
|
||||
dependencies:
|
||||
is-plain-object "^2.0.4"
|
||||
kind-of "^6.0.2"
|
||||
shallow-clone "^3.0.0"
|
||||
|
||||
core-js@^3.1.2:
|
||||
version "3.14.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.14.0.tgz#62322b98c71cc2018b027971a69419e2425c2a6c"
|
||||
integrity sha512-3s+ed8er9ahK+zJpp9ZtuVcDoFzHNiZsPbNAAE4KXgrRHbjSqqNN6xGSXq6bq7TZIbKj4NLrLb6bJ5i+vSVjHA==
|
||||
version "3.15.2"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61"
|
||||
integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==
|
||||
|
||||
debug@4:
|
||||
version "4.3.1"
|
||||
@@ -282,13 +273,6 @@ is-number-object@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb"
|
||||
integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==
|
||||
|
||||
is-plain-object@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
||||
integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
is-regex@^1.1.1, is-regex@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
|
||||
@@ -340,11 +324,6 @@ isarray@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
||||
|
||||
isobject@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||
|
||||
js-yaml@^4.0.0, js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
@@ -361,11 +340,6 @@ jsonfile@^6.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
kind-of@^6.0.2:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||
|
||||
lazy-val@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.4.tgz#882636a7245c2cfe6e0a4e3ba6c5d68a137e5c65"
|
||||
@@ -494,13 +468,6 @@ semver@^7.3.5:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
shallow-clone@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
|
||||
integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
|
||||
dependencies:
|
||||
kind-of "^6.0.2"
|
||||
|
||||
side-channel@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-electron",
|
||||
"version": "1.0.144",
|
||||
"version": "1.0.145",
|
||||
"description": "Electron-specific bindings",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
@@ -92,10 +92,10 @@ export default class ElectronModule {
|
||||
|
||||
try {
|
||||
let electronKeySpec = item[0]
|
||||
electronKeySpec = electronKeySpec.replace('Meta', 'Super')
|
||||
electronKeySpec = electronKeySpec.replace('⌘', 'Command')
|
||||
electronKeySpec = electronKeySpec.replace('⌥', 'Alt')
|
||||
electronKeySpec = electronKeySpec.replace(/-/g, '+')
|
||||
electronKeySpec = electronKeySpec.replaceAll('Meta', 'Super')
|
||||
electronKeySpec = electronKeySpec.replaceAll('⌘', 'Command')
|
||||
electronKeySpec = electronKeySpec.replaceAll('⌥', 'Alt')
|
||||
electronKeySpec = electronKeySpec.replaceAll('-', '+')
|
||||
specs.push(electronKeySpec)
|
||||
} catch (err) {
|
||||
console.error('Could not register the global hotkey:', err)
|
||||
|
@@ -120,7 +120,7 @@ export class ElectronUpdaterService extends UpdaterService {
|
||||
|
||||
async update (): Promise<void> {
|
||||
if (!this.electronUpdaterAvailable) {
|
||||
this.electron.shell.openExternal(this.updateURL)
|
||||
await this.electron.shell.openExternal(this.updateURL)
|
||||
} else {
|
||||
if ((await this.platform.showMessageBox(
|
||||
{
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-local",
|
||||
"version": "1.0.144",
|
||||
"version": "1.0.145",
|
||||
"description": "Tabby's local shell plugin",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
@@ -29,7 +29,6 @@
|
||||
"ps-node": "^0.1.6",
|
||||
"runes": "^0.4.2",
|
||||
"shell-escape": "^0.2.0",
|
||||
"slugify": "^1.5.3",
|
||||
"utils-decorators": "^1.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { Profile } from 'tabby-core'
|
||||
|
||||
export interface Shell {
|
||||
id: string
|
||||
name?: string
|
||||
name: string
|
||||
command: string
|
||||
args?: string[]
|
||||
env: Record<string, string>
|
||||
@@ -40,14 +42,8 @@ export interface SessionOptions {
|
||||
runAsAdministrator?: boolean
|
||||
}
|
||||
|
||||
export interface Profile {
|
||||
name: string
|
||||
color?: string
|
||||
sessionOptions: SessionOptions
|
||||
shell?: string
|
||||
isBuiltin?: boolean
|
||||
icon?: string
|
||||
disableDynamicTitle?: boolean
|
||||
export interface LocalProfile extends Profile {
|
||||
options: SessionOptions
|
||||
}
|
||||
|
||||
export interface ChildProcess {
|
||||
|
@@ -1,37 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ToolbarButtonProvider, ToolbarButton, ConfigService, SelectorOption, SelectorService } from 'tabby-core'
|
||||
import { ElectronService } from 'tabby-electron'
|
||||
|
||||
import { ToolbarButtonProvider, ToolbarButton } from 'tabby-core'
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class ButtonProvider extends ToolbarButtonProvider {
|
||||
constructor (
|
||||
electron: ElectronService,
|
||||
private selector: SelectorService,
|
||||
private config: ConfigService,
|
||||
private terminal: TerminalService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async activate () {
|
||||
const options: SelectorOption<void>[] = []
|
||||
const profiles = await this.terminal.getProfiles({ skipDefault: !this.config.store.terminal.showDefaultProfiles })
|
||||
|
||||
for (const profile of profiles) {
|
||||
options.push({
|
||||
icon: profile.icon,
|
||||
name: profile.name,
|
||||
callback: () => this.terminal.openTab(profile),
|
||||
})
|
||||
}
|
||||
|
||||
await this.selector.show('Select profile', options)
|
||||
}
|
||||
|
||||
provide (): ToolbarButton[] {
|
||||
return [
|
||||
{
|
||||
@@ -42,11 +22,6 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
||||
this.terminal.openTab()
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: require('./icons/profiles.svg'),
|
||||
title: 'New terminal with profile',
|
||||
click: () => this.activate(),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,6 @@ export class TerminalCLIHandler extends CLIHandler {
|
||||
priority = 0
|
||||
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
private hostWindow: HostWindowService,
|
||||
private terminal: TerminalService,
|
||||
) {
|
||||
@@ -24,8 +23,6 @@ export class TerminalCLIHandler extends CLIHandler {
|
||||
this.handleOpenDirectory(path.resolve(event.cwd, event.argv.directory))
|
||||
} else if (op === 'run') {
|
||||
this.handleRunCommand(event.argv.command)
|
||||
} else if (op === 'profile') {
|
||||
this.handleOpenProfile(event.argv.profileName)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@@ -47,24 +44,15 @@ export class TerminalCLIHandler extends CLIHandler {
|
||||
|
||||
private handleRunCommand (command: string[]) {
|
||||
this.terminal.openTab({
|
||||
type: 'local',
|
||||
name: '',
|
||||
sessionOptions: {
|
||||
options: {
|
||||
command: command[0],
|
||||
args: command.slice(1),
|
||||
},
|
||||
}, null, true)
|
||||
this.hostWindow.bringToFront()
|
||||
}
|
||||
|
||||
private handleOpenProfile (profileName: string) {
|
||||
const profile = this.config.store.terminal.profiles.find(x => x.name === profileName)
|
||||
if (!profile) {
|
||||
console.error('Requested profile', profileName, 'not found')
|
||||
return
|
||||
}
|
||||
this.terminal.openTabWithOptions(profile.sessionOptions)
|
||||
this.hostWindow.bringToFront()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,73 +0,0 @@
|
||||
.modal-body
|
||||
.form-group
|
||||
label Name
|
||||
input.form-control(
|
||||
type='text',
|
||||
autofocus,
|
||||
[(ngModel)]='profile.name',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Command
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.command',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Arguments
|
||||
.input-group(
|
||||
*ngFor='let arg of profile.sessionOptions.args; index as i; trackBy: trackByIndex',
|
||||
)
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.args[i]',
|
||||
)
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='profile.sessionOptions.args.splice(i, 1)')
|
||||
i.fas.fa-trash
|
||||
|
||||
.mt-2
|
||||
button.btn.btn-secondary((click)='profile.sessionOptions.args.push("")')
|
||||
i.fas.fa-plus.mr-2
|
||||
| Add
|
||||
|
||||
.form-line(*ngIf='uac.isAvailable')
|
||||
.header
|
||||
.title Run as administrator
|
||||
toggle(
|
||||
[(ngModel)]='profile.sessionOptions.runAsAdministrator',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Working directory
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.cwd',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Environment
|
||||
environment-editor(
|
||||
type='text',
|
||||
[(model)]='profile.sessionOptions.env',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Tab color
|
||||
input.form-control(
|
||||
type='text',
|
||||
autofocus,
|
||||
[(ngModel)]='profile.color',
|
||||
placeholder='#000000'
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Disable dynamic tab title
|
||||
.description Connection name will be used as a title instead
|
||||
toggle([(ngModel)]='profile.disableDynamicTitle')
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-outline-primary((click)='save()') Save
|
||||
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
@@ -1,36 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { UACService } from '../services/uac.service'
|
||||
import { Profile } from '../api'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
template: require('./editProfileModal.component.pug'),
|
||||
})
|
||||
export class EditProfileModalComponent {
|
||||
profile: Profile
|
||||
|
||||
constructor (
|
||||
public uac: UACService,
|
||||
private modalInstance: NgbActiveModal,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.profile.sessionOptions.env = this.profile.sessionOptions.env ?? {}
|
||||
this.profile.sessionOptions.args = this.profile.sessionOptions.args ?? []
|
||||
}
|
||||
|
||||
save () {
|
||||
this.modalInstance.close(this.profile)
|
||||
}
|
||||
|
||||
cancel () {
|
||||
this.modalInstance.dismiss()
|
||||
}
|
||||
|
||||
trackByIndex (index) {
|
||||
return index
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
.form-group
|
||||
label Command
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.options.command',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Arguments
|
||||
.input-group(
|
||||
*ngFor='let arg of profile.options.args; index as i; trackBy: trackByIndex',
|
||||
)
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.options.args[i]',
|
||||
)
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='profile.options.args.splice(i, 1)')
|
||||
i.fas.fa-trash
|
||||
|
||||
.mt-2
|
||||
button.btn.btn-secondary((click)='profile.options.args.push("")')
|
||||
i.fas.fa-plus.mr-2
|
||||
| Add
|
||||
|
||||
.form-line(*ngIf='uac.isAvailable')
|
||||
.header
|
||||
.title Run as administrator
|
||||
toggle(
|
||||
[(ngModel)]='profile.options.runAsAdministrator',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Working directory
|
||||
|
||||
.input-group
|
||||
input.form-control(
|
||||
type='text',
|
||||
placeholder='Home directory',
|
||||
[(ngModel)]='profile.options.cwd'
|
||||
)
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='pickWorkingDirectory()')
|
||||
i.fas.fa-folder-open
|
||||
|
||||
.form-group
|
||||
label Environment
|
||||
environment-editor(
|
||||
type='text',
|
||||
[(model)]='profile.options.env',
|
||||
)
|
47
tabby-local/src/components/localProfileSettings.component.ts
Normal file
47
tabby-local/src/components/localProfileSettings.component.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component } from '@angular/core'
|
||||
import { UACService } from '../services/uac.service'
|
||||
import { LocalProfile } from '../api'
|
||||
import { ElectronHostWindow, ElectronService } from 'tabby-electron'
|
||||
import { ProfileSettingsComponent } from '../../../tabby-core/src/api/profileProvider'
|
||||
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
template: require('./localProfileSettings.component.pug'),
|
||||
})
|
||||
export class LocalProfileSettingsComponent implements ProfileSettingsComponent {
|
||||
profile: LocalProfile
|
||||
|
||||
constructor (
|
||||
public uac: UACService,
|
||||
private hostWindow: ElectronHostWindow,
|
||||
private electron: ElectronService,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.profile.options.env = this.profile.options.env ?? {}
|
||||
this.profile.options.args = this.profile.options.args ?? []
|
||||
}
|
||||
|
||||
async pickWorkingDirectory (): Promise<void> {
|
||||
// const profile = await this.terminal.getProfileByID(this.config.store.terminal.profile)
|
||||
// const shell = this.shells.find(x => x.id === profile?.shell)
|
||||
// if (!shell) {
|
||||
// return
|
||||
// }
|
||||
const paths = (await this.electron.dialog.showOpenDialog(
|
||||
this.hostWindow.getWindow(),
|
||||
{
|
||||
// TODO
|
||||
// defaultPath: shell.fsBase,
|
||||
properties: ['openDirectory', 'showHiddenFiles'],
|
||||
}
|
||||
)).filePaths
|
||||
this.profile.options.cwd = paths[0]
|
||||
}
|
||||
|
||||
trackByIndex (index) {
|
||||
return index
|
||||
}
|
||||
}
|
@@ -1,20 +1,5 @@
|
||||
h3.mb-3 Shell
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Profile
|
||||
.description Default profile for new tabs
|
||||
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.profile',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
option(
|
||||
*ngFor='let profile of profiles',
|
||||
[ngValue]='terminal.getProfileID(profile)'
|
||||
) {{profile.name}}
|
||||
|
||||
|
||||
.form-line(*ngIf='isConPTYAvailable')
|
||||
.header
|
||||
.title Use ConPTY
|
||||
@@ -30,75 +15,3 @@ h3.mb-3 Shell
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.profile.startsWith("WSL") && (!config.store.terminal.useConPTY)')
|
||||
.mr-auto WSL terminal only supports TrueColor with ConPTY
|
||||
|
||||
.form-line(*ngIf='config.store.terminal.profile == "custom-shell"')
|
||||
.header
|
||||
.title Custom shell
|
||||
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='config.store.terminal.customShell',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Working directory
|
||||
.input-group
|
||||
input.form-control(
|
||||
type='text',
|
||||
placeholder='Home directory',
|
||||
[(ngModel)]='config.store.terminal.workingDirectory',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='pickWorkingDirectory()')
|
||||
i.fas.fa-folder-open
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Directory for new tabs
|
||||
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.alwaysUseWorkingDirectory',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
option([ngValue]='false') Same as active tab's directory
|
||||
option([ngValue]='true') The working directory from above
|
||||
|
||||
.form-line.align-items-start
|
||||
.header
|
||||
.title Environment
|
||||
.description Inject additional environment variables
|
||||
|
||||
environment-editor([(model)]='this.config.store.terminal.environment')
|
||||
|
||||
.form-line(*ngIf='config.store.terminal.profiles.length > 0')
|
||||
.header
|
||||
.title Show default profiles in the selector
|
||||
.description If disabled, only custom profiles will show up in the profile selector
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.showDefaultProfiles',
|
||||
(ngModelChange)='config.save()'
|
||||
)
|
||||
|
||||
h3.mt-3 Saved Profiles
|
||||
|
||||
.list-group.list-group-flush.mt-3.mb-3
|
||||
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||
*ngFor='let profile of config.store.terminal.profiles',
|
||||
(click)='editProfile(profile)',
|
||||
)
|
||||
.mr-auto
|
||||
div {{profile.name}}
|
||||
.text-muted {{profile.sessionOptions.command}}
|
||||
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteProfile(profile)')
|
||||
i.fas.fa-trash
|
||||
|
||||
.pb-4(ngbDropdown, placement='top-left')
|
||||
button.btn.btn-primary(ngbDropdownToggle)
|
||||
i.fas.fa-fw.fa-plus
|
||||
| New profile
|
||||
div(ngbDropdownMenu)
|
||||
button.dropdown-item(*ngFor='let shell of shells', (click)='newProfile(shell)') {{shell.name}}
|
||||
|
@@ -1,93 +1,18 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { ConfigService, HostAppService, Platform, WIN_BUILD_CONPTY_SUPPORTED, WIN_BUILD_CONPTY_STABLE, isWindowsBuild } from 'tabby-core'
|
||||
import { ElectronService, ElectronHostWindow } from 'tabby-electron'
|
||||
import { EditProfileModalComponent } from './editProfileModal.component'
|
||||
import { Shell, Profile } from '../api'
|
||||
import { TerminalService } from '../services/terminal.service'
|
||||
import { WIN_BUILD_CONPTY_SUPPORTED, WIN_BUILD_CONPTY_STABLE, isWindowsBuild, ConfigService } from 'tabby-core'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
template: require('./shellSettingsTab.component.pug'),
|
||||
})
|
||||
export class ShellSettingsTabComponent {
|
||||
shells: Shell[] = []
|
||||
profiles: Profile[] = []
|
||||
Platform = Platform
|
||||
isConPTYAvailable: boolean
|
||||
isConPTYStable: boolean
|
||||
private configSubscription: Subscription
|
||||
|
||||
constructor (
|
||||
public config: ConfigService,
|
||||
public hostApp: HostAppService,
|
||||
public hostWindow: ElectronHostWindow,
|
||||
public terminal: TerminalService,
|
||||
private electron: ElectronService,
|
||||
private ngbModal: NgbModal,
|
||||
) {
|
||||
config.store.terminal.environment = config.store.terminal.environment || {}
|
||||
this.configSubscription = this.config.changed$.subscribe(() => {
|
||||
this.reload()
|
||||
})
|
||||
this.reload()
|
||||
|
||||
this.isConPTYAvailable = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED)
|
||||
this.isConPTYStable = isWindowsBuild(WIN_BUILD_CONPTY_STABLE)
|
||||
}
|
||||
|
||||
async ngOnInit (): Promise<void> {
|
||||
this.shells = (await this.terminal.shells$.toPromise())!
|
||||
}
|
||||
|
||||
ngOnDestroy (): void {
|
||||
this.configSubscription.unsubscribe()
|
||||
}
|
||||
|
||||
async reload (): Promise<void> {
|
||||
this.profiles = await this.terminal.getProfiles({ includeHidden: true })
|
||||
}
|
||||
|
||||
async pickWorkingDirectory (): Promise<void> {
|
||||
const profile = await this.terminal.getProfileByID(this.config.store.terminal.profile)
|
||||
const shell = this.shells.find(x => x.id === profile?.shell)
|
||||
if (!shell) {
|
||||
return
|
||||
}
|
||||
const paths = (await this.electron.dialog.showOpenDialog(
|
||||
this.hostWindow.getWindow(),
|
||||
{
|
||||
defaultPath: shell.fsBase,
|
||||
properties: ['openDirectory', 'showHiddenFiles'],
|
||||
}
|
||||
)).filePaths
|
||||
this.config.store.terminal.workingDirectory = paths[0]
|
||||
}
|
||||
|
||||
newProfile (shell: Shell): void {
|
||||
const profile: Profile = {
|
||||
name: shell.name ?? '',
|
||||
shell: shell.id,
|
||||
sessionOptions: this.terminal.optionsFromShell(shell),
|
||||
}
|
||||
this.config.store.terminal.profiles = [profile, ...this.config.store.terminal.profiles]
|
||||
this.config.save()
|
||||
this.reload()
|
||||
}
|
||||
|
||||
editProfile (profile: Profile): void {
|
||||
const modal = this.ngbModal.open(EditProfileModalComponent)
|
||||
modal.componentInstance.profile = Object.assign({}, profile)
|
||||
modal.result.then(result => {
|
||||
Object.assign(profile, result)
|
||||
this.config.save()
|
||||
})
|
||||
}
|
||||
|
||||
deleteProfile (profile: Profile): void {
|
||||
this.config.store.terminal.profiles = this.config.store.terminal.profiles.filter(x => x !== profile)
|
||||
this.config.save()
|
||||
this.reload()
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabb
|
||||
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
||||
import { SessionOptions } from '../api'
|
||||
import { Session } from '../session'
|
||||
import { UACService } from '../services/uac.service'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
@@ -18,6 +19,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||
constructor (
|
||||
injector: Injector,
|
||||
private uac: UACService,
|
||||
) {
|
||||
super(injector)
|
||||
}
|
||||
@@ -28,7 +30,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
||||
|
||||
const isConPTY = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY
|
||||
|
||||
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, hotkey => {
|
||||
this.subscribeUntilDestroyed(this.hotkeys.hotkey$, hotkey => {
|
||||
if (!this.hasFocus) {
|
||||
return
|
||||
}
|
||||
@@ -52,6 +54,10 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
||||
}
|
||||
|
||||
initializeSession (columns: number, rows: number): void {
|
||||
if (this.sessionOptions.runAsAdministrator && this.uac.isAvailable) {
|
||||
this.sessionOptions = this.uac.patchSessionOptionsForUAC(this.sessionOptions)
|
||||
}
|
||||
|
||||
this.session!.start({
|
||||
...this.sessionOptions,
|
||||
width: columns,
|
||||
|
@@ -5,30 +5,19 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
defaults = {
|
||||
hotkeys: {
|
||||
'copy-current-path': [],
|
||||
shell: {
|
||||
__nonStructural: true,
|
||||
},
|
||||
profile: {
|
||||
__nonStructural: true,
|
||||
},
|
||||
},
|
||||
terminal: {
|
||||
autoOpen: false,
|
||||
customShell: '',
|
||||
workingDirectory: '',
|
||||
alwaysUseWorkingDirectory: false,
|
||||
useConPTY: true,
|
||||
showDefaultProfiles: true,
|
||||
environment: {},
|
||||
profiles: [],
|
||||
setComSpec: false,
|
||||
},
|
||||
}
|
||||
|
||||
platformDefaults = {
|
||||
[Platform.macOS]: {
|
||||
terminal: {
|
||||
shell: 'default',
|
||||
profile: 'user-default',
|
||||
profile: 'local:default',
|
||||
},
|
||||
hotkeys: {
|
||||
'new-tab': [
|
||||
@@ -38,8 +27,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
},
|
||||
[Platform.Windows]: {
|
||||
terminal: {
|
||||
shell: 'clink',
|
||||
profile: 'cmd-clink',
|
||||
profile: 'local:cmd-clink',
|
||||
},
|
||||
hotkeys: {
|
||||
'new-tab': [
|
||||
@@ -49,8 +37,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
},
|
||||
[Platform.Linux]: {
|
||||
terminal: {
|
||||
shell: 'default',
|
||||
profile: 'user-default',
|
||||
profile: 'local:default',
|
||||
},
|
||||
hotkeys: {
|
||||
'new-tab': [
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@@ -12,18 +11,7 @@ export class LocalTerminalHotkeyProvider extends HotkeyProvider {
|
||||
},
|
||||
]
|
||||
|
||||
constructor (
|
||||
private terminal: TerminalService,
|
||||
) { super() }
|
||||
|
||||
async provide (): Promise<HotkeyDescription[]> {
|
||||
const profiles = await this.terminal.getProfiles()
|
||||
return [
|
||||
...this.hotkeys,
|
||||
...profiles.map(profile => ({
|
||||
id: `profile.${this.terminal.getProfileID(profile)}`,
|
||||
name: `New tab: ${profile.name}`,
|
||||
})),
|
||||
]
|
||||
return this.hotkeys
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="svg-inline--fa fa-terminal fa-w-20 fa-2x" data-icon="terminal" data-prefix="fas" focusable="false" role="img" viewBox="0 0 640 512"><path fill="purple" stroke="none" stroke-width="1" d="M257.981 272.971L63.638 467.314c-9.373 9.373-24.569 9.373-33.941 0L7.029 444.647c-9.357-9.357-9.375-24.522-.04-33.901L161.011 256 6.99 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L257.981 239.03c9.373 9.372 9.373 24.568 0 33.941zM640 456v-32c0-13.255-10.745-24-24-24H312c-13.255 0-24 10.745-24 24v32c0 13.255 10.745 24 24 24h304c13.255 0 24-10.745 24-24z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="svg-inline--fa fa-terminal fa-w-20 fa-2x" data-icon="terminal" data-prefix="fas" focusable="false" role="img" viewBox="0 0 640 512"><path fill="#ef4eff" stroke="none" stroke-width="1" d="M257.981 272.971L63.638 467.314c-9.373 9.373-24.569 9.373-33.941 0L7.029 444.647c-9.357-9.357-9.375-24.522-.04-33.901L161.011 256 6.99 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L257.981 239.03c9.373 9.372 9.373 24.568 0 33.941zM640 456v-32c0-13.255-10.745-24-24-24H312c-13.255 0-24 10.745-24 24v32c0 13.255 10.745 24 24 24h304c13.255 0 24-10.745 24-24z"/></svg>
|
||||
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 664 B |
1
tabby-local/src/icons/vs.svg
Normal file
1
tabby-local/src/icons/vs.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 96 95.51"><defs><style>.a{fill:#fff;}.a,.h{fill-rule:evenodd;}.b{mask:url(#a);}.c{fill:#52218a;}.d{fill:#6c33af;}.e{fill:#854cc7;}.f{fill:#b179f1;}.g{opacity:0.25;}.h{fill:url(#b);}</style><mask id="a" x="0" y="0" width="96" height="95.51" maskUnits="userSpaceOnUse"><g transform="translate(0 -0.25)"><path class="a" d="M68.89,95.6a6,6,0,0,0,3.93-.44L92.6,85.65A6,6,0,0,0,96,80.24V15.76a6,6,0,0,0-3.4-5.41L72.82.84A6,6,0,0,0,68.34.55,6,6,0,0,0,66,2L34.12,37.26,15.5,22l-1.63-1.4a4,4,0,0,0-3.61-.83,2.55,2.55,0,0,0-.53.18L2.46,23A4,4,0,0,0,0,26.37c0,.1,0,.2,0,.3V69.33c0,.1,0,.2,0,.3A4,4,0,0,0,2.46,73l7.27,3a2.55,2.55,0,0,0,.53.18,4,4,0,0,0,3.61-.83L15.5,74,34.12,58.74,66,94A6,6,0,0,0,68.89,95.6ZM72,27.68,47.21,48,72,68.32ZM12,34.27,24.41,48,12,61.73Z"></path></g></mask><linearGradient id="b" x1="48" y1="97.75" x2="48" y2="2.25" gradientTransform="matrix(1, 0, 0, -1, 0, 98)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"></stop><stop offset="1" stop-color="#fff" stop-opacity="0"></stop></linearGradient></defs><title>BrandVisualStudioWin2019</title><g class="b"><path class="c" d="M13.87,75.4a4,4,0,0,1-4.14.65L2.46,73A4,4,0,0,1,0,69.33V26.67A4,4,0,0,1,2.46,23l7.27-3a4,4,0,0,1,4.14.65L15.5,22A2.21,2.21,0,0,0,12,23.8V72.2A2.21,2.21,0,0,0,15.5,74Z" transform="translate(0 -0.25)"></path><path class="d" d="M2.46,73A4,4,0,0,1,0,69.33V69a2.31,2.31,0,0,0,4,1.55L66,2A6,6,0,0,1,72.82.84L92.6,10.36A6,6,0,0,1,96,15.77V16a3.79,3.79,0,0,0-6.19-2.93L15.5,74l-1.63,1.4a4,4,0,0,1-4.14.65Z" transform="translate(0 -0.25)"></path><path class="e" d="M2.46,23A4,4,0,0,0,0,26.67V27a2.31,2.31,0,0,1,4-1.55L66,94a6,6,0,0,0,6.82,1.16L92.6,85.64A6,6,0,0,0,96,80.23V80a3.79,3.79,0,0,1-6.19,2.93L15.5,22l-1.63-1.4A4,4,0,0,0,9.73,20Z" transform="translate(0 -0.25)"></path><path class="f" d="M72.82,95.16A6,6,0,0,1,66,94a3.52,3.52,0,0,0,6-2.49v-87A3.52,3.52,0,0,0,66,2,6,6,0,0,1,72.82.84L92.6,10.35A6,6,0,0,1,96,15.76V80.24a6,6,0,0,1-3.4,5.41Z" transform="translate(0 -0.25)"></path><g class="g"><path class="h" d="M68.89,95.6a6,6,0,0,0,3.93-.44L92.6,85.65A6,6,0,0,0,96,80.24V15.76a6,6,0,0,0-3.4-5.41L72.82.84A6,6,0,0,0,68.34.55,6,6,0,0,0,66,2L34.12,37.26,15.5,22l-1.63-1.4a4,4,0,0,0-3.61-.83,2.55,2.55,0,0,0-.53.18L2.46,23A4,4,0,0,0,0,26.37c0,.1,0,.2,0,.3V69.33c0,.1,0,.2,0,.3A4,4,0,0,0,2.46,73l7.27,3a2.55,2.55,0,0,0,.53.18,4,4,0,0,0,3.61-.83L15.5,74,34.12,58.74,66,94A6,6,0,0,0,68.89,95.6ZM72,27.68,47.21,48,72,68.32ZM12,34.27,24.41,48,12,61.73Z" transform="translate(0 -0.25)"></path></g></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
@@ -4,15 +4,15 @@ import { FormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ToastrModule } from 'ngx-toastr'
|
||||
|
||||
import TabbyCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, TabContextMenuItemProvider, CLIHandler, ConfigService } from 'tabby-core'
|
||||
import TabbyCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, TabContextMenuItemProvider, CLIHandler, ConfigService, ProfileProvider } from 'tabby-core'
|
||||
import TabbyTerminalModule from 'tabby-terminal'
|
||||
import TabbyElectronPlugin from 'tabby-electron'
|
||||
import { SettingsTabProvider } from 'tabby-settings'
|
||||
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
|
||||
import { EditProfileModalComponent } from './components/editProfileModal.component'
|
||||
import { EnvironmentEditorComponent } from './components/environmentEditor.component'
|
||||
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
|
||||
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
import { DockMenuService } from './services/dockMenu.service'
|
||||
@@ -26,7 +26,6 @@ import { LocalTerminalHotkeyProvider } from './hotkeys'
|
||||
import { NewTabContextMenu, SaveAsProfileContextMenu } from './tabContextMenu'
|
||||
|
||||
import { CmderShellProvider } from './shells/cmder'
|
||||
import { CustomShellProvider } from './shells/custom'
|
||||
import { Cygwin32ShellProvider } from './shells/cygwin32'
|
||||
import { Cygwin64ShellProvider } from './shells/cygwin64'
|
||||
import { GitBashShellProvider } from './shells/gitBash'
|
||||
@@ -37,8 +36,10 @@ import { PowerShellCoreShellProvider } from './shells/powershellCore'
|
||||
import { WindowsDefaultShellProvider } from './shells/winDefault'
|
||||
import { WindowsStockShellsProvider } from './shells/windowsStock'
|
||||
import { WSLShellProvider } from './shells/wsl'
|
||||
import { VSDevToolsProvider } from './shells/vs'
|
||||
|
||||
import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from './cli'
|
||||
import { LocalProfilesService } from './profiles'
|
||||
|
||||
/** @hidden */
|
||||
@NgModule({
|
||||
@@ -65,12 +66,14 @@ import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from '.
|
||||
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: PowerShellCoreShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: CmderShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: CustomShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: GitBashShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: POSIXShellsProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: WSLShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: VSDevToolsProvider, multi: true },
|
||||
|
||||
{ provide: ProfileProvider, useClass: LocalProfilesService, multi: true },
|
||||
|
||||
{ provide: TabContextMenuItemProvider, useClass: NewTabContextMenu, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: SaveAsProfileContextMenu, multi: true },
|
||||
@@ -87,13 +90,13 @@ import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from '.
|
||||
entryComponents: [
|
||||
TerminalTabComponent,
|
||||
ShellSettingsTabComponent,
|
||||
EditProfileModalComponent,
|
||||
LocalProfileSettingsComponent,
|
||||
] as any[],
|
||||
declarations: [
|
||||
TerminalTabComponent,
|
||||
ShellSettingsTabComponent,
|
||||
EditProfileModalComponent,
|
||||
EnvironmentEditorComponent,
|
||||
LocalProfileSettingsComponent,
|
||||
] as any[],
|
||||
exports: [
|
||||
TerminalTabComponent,
|
||||
@@ -108,19 +111,13 @@ export default class LocalTerminalModule { // eslint-disable-line @typescript-es
|
||||
dockMenu: DockMenuService,
|
||||
config: ConfigService,
|
||||
) {
|
||||
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
||||
hotkeys.hotkey$.subscribe(async (hotkey) => {
|
||||
if (hotkey === 'new-tab') {
|
||||
terminal.openTab()
|
||||
}
|
||||
if (hotkey === 'new-window') {
|
||||
hostApp.newWindow()
|
||||
}
|
||||
if (hotkey.startsWith('profile.')) {
|
||||
const profile = await terminal.getProfileByID(hotkey.split('.')[1])
|
||||
if (profile) {
|
||||
terminal.openTabWithOptions(profile.sessionOptions)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
config.ready$.toPromise().then(() => {
|
||||
|
72
tabby-local/src/profiles.ts
Normal file
72
tabby-local/src/profiles.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { ProfileProvider, Profile, NewTabParameters, ConfigService, SplitTabComponent, AppService } from 'tabby-core'
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
|
||||
import { ShellProvider, Shell, SessionOptions } from './api'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LocalProfilesService extends ProfileProvider {
|
||||
id = 'local'
|
||||
name = 'Local'
|
||||
settingsComponent = LocalProfileSettingsComponent
|
||||
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private config: ConfigService,
|
||||
@Inject(ShellProvider) private shellProviders: ShellProvider[],
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async getBuiltinProfiles (): Promise<Profile[]> {
|
||||
return (await this.getShells()).map(shell => ({
|
||||
id: `local:${shell.id}`,
|
||||
type: 'local',
|
||||
name: shell.name,
|
||||
icon: shell.icon,
|
||||
options: this.optionsFromShell(shell),
|
||||
isBuiltin: true,
|
||||
}))
|
||||
}
|
||||
|
||||
async getNewTabParameters (profile: Profile): Promise<NewTabParameters<TerminalTabComponent>> {
|
||||
const options = { ...profile.options }
|
||||
|
||||
if (!options.cwd) {
|
||||
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
|
||||
options.cwd = await this.app.activeTab.session.getWorkingDirectory()
|
||||
}
|
||||
if (this.app.activeTab instanceof SplitTabComponent) {
|
||||
const focusedTab = this.app.activeTab.getFocusedTab()
|
||||
|
||||
if (focusedTab instanceof TerminalTabComponent && focusedTab.session) {
|
||||
options.cwd = await focusedTab.session.getWorkingDirectory()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: TerminalTabComponent,
|
||||
inputs: {
|
||||
sessionOptions: options,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async getShells (): Promise<Shell[]> {
|
||||
const shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
|
||||
return shellLists.reduce((a, b) => a.concat(b), [])
|
||||
}
|
||||
|
||||
optionsFromShell (shell: Shell): SessionOptions {
|
||||
return {
|
||||
command: shell.command,
|
||||
args: shell.args ?? [],
|
||||
env: shell.env,
|
||||
}
|
||||
}
|
||||
|
||||
getDescription (profile: Profile): string {
|
||||
return profile.options?.command
|
||||
}
|
||||
}
|
@@ -1,19 +1,19 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from 'tabby-core'
|
||||
import { TabRecoveryProvider, NewTabParameters, RecoveryToken } from 'tabby-core'
|
||||
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class RecoveryProvider extends TabRecoveryProvider {
|
||||
export class RecoveryProvider extends TabRecoveryProvider<TerminalTabComponent> {
|
||||
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
||||
return recoveryToken.type === 'app:terminal-tab'
|
||||
}
|
||||
|
||||
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab> {
|
||||
async recover (recoveryToken: RecoveryToken): Promise<NewTabParameters<TerminalTabComponent>> {
|
||||
return {
|
||||
type: TerminalTabComponent,
|
||||
options: {
|
||||
inputs: {
|
||||
sessionOptions: recoveryToken.sessionOptions,
|
||||
savedState: recoveryToken.savedState,
|
||||
},
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { NgZone, Injectable } from '@angular/core'
|
||||
import { ConfigService, HostAppService, Platform } from 'tabby-core'
|
||||
import { ConfigService, HostAppService, Platform, ProfilesService } from 'tabby-core'
|
||||
import { ElectronService } from 'tabby-electron'
|
||||
import { TerminalService } from './terminal.service'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -13,17 +12,17 @@ export class DockMenuService {
|
||||
private config: ConfigService,
|
||||
private hostApp: HostAppService,
|
||||
private zone: NgZone,
|
||||
private terminalService: TerminalService,
|
||||
private profilesService: ProfilesService,
|
||||
) {
|
||||
config.changed$.subscribe(() => this.update())
|
||||
}
|
||||
|
||||
update (): void {
|
||||
if (this.hostApp.platform === Platform.Windows) {
|
||||
this.electron.app.setJumpList(this.config.store.terminal.profiles.length ? [{
|
||||
this.electron.app.setJumpList(this.config.store.profiles.length ? [{
|
||||
type: 'custom',
|
||||
name: 'Profiles',
|
||||
items: this.config.store.terminal.profiles.map(profile => ({
|
||||
items: this.config.store.profiles.map(profile => ({
|
||||
type: 'task',
|
||||
program: process.execPath,
|
||||
args: `profile "${profile.name}"`,
|
||||
@@ -35,10 +34,10 @@ export class DockMenuService {
|
||||
}
|
||||
if (this.hostApp.platform === Platform.macOS) {
|
||||
this.electron.app.dock.setMenu(this.electron.Menu.buildFromTemplate(
|
||||
this.config.store.terminal.profiles.map(profile => ({
|
||||
this.config.store.profiles.map(profile => ({
|
||||
label: profile.name,
|
||||
click: () => this.zone.run(() => {
|
||||
this.terminalService.openTabWithOptions(profile.sessionOptions)
|
||||
click: () => this.zone.run(async () => {
|
||||
this.profilesService.openNewTabForProfile(profile)
|
||||
}),
|
||||
}))
|
||||
))
|
||||
|
@@ -1,150 +1,69 @@
|
||||
import * as fs from 'mz/fs'
|
||||
import slugify from 'slugify'
|
||||
import { Observable, AsyncSubject } from 'rxjs'
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { AppService, Logger, LogService, ConfigService, SplitTabComponent } from 'tabby-core'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Logger, LogService, ConfigService, AppService, ProfilesService } from 'tabby-core'
|
||||
import { TerminalTabComponent } from '../components/terminalTab.component'
|
||||
import { ShellProvider, Shell, SessionOptions, Profile } from '../api'
|
||||
import { UACService } from './uac.service'
|
||||
import { SessionOptions, LocalProfile } from '../api'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TerminalService {
|
||||
private shells = new AsyncSubject<Shell[]>()
|
||||
private logger: Logger
|
||||
|
||||
/**
|
||||
* A fresh list of all available shells
|
||||
*/
|
||||
get shells$ (): Observable<Shell[]> { return this.shells }
|
||||
|
||||
/** @hidden */
|
||||
private constructor (
|
||||
private app: AppService,
|
||||
private profilesService: ProfilesService,
|
||||
private config: ConfigService,
|
||||
private uac: UACService,
|
||||
@Inject(ShellProvider) private shellProviders: ShellProvider[],
|
||||
log: LogService,
|
||||
) {
|
||||
this.logger = log.create('terminal')
|
||||
|
||||
config.ready$.toPromise().then(() => {
|
||||
this.reloadShells()
|
||||
config.changed$.subscribe(() => {
|
||||
this.reloadShells()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async getProfiles ({ includeHidden, skipDefault }: { includeHidden?: boolean, skipDefault?: boolean } = {}): Promise<Profile[]> {
|
||||
const shells = (await this.shells$.toPromise())!
|
||||
return [
|
||||
...this.config.store.terminal.profiles,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
...skipDefault ? [] : shells.filter(x => includeHidden || !x.hidden).map(shell => ({
|
||||
name: shell.name,
|
||||
shell: shell.id,
|
||||
icon: shell.icon,
|
||||
sessionOptions: this.optionsFromShell(shell),
|
||||
isBuiltin: true,
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
getProfileID (profile: Profile): string {
|
||||
return slugify(profile.name, { remove: /[:.]/g }).toLowerCase()
|
||||
}
|
||||
|
||||
async getProfileByID (id: string): Promise<Profile|null> {
|
||||
const profiles = await this.getProfiles({ includeHidden: true })
|
||||
return profiles.find(x => this.getProfileID(x) === id) ?? null
|
||||
async getDefaultProfile (): Promise<LocalProfile> {
|
||||
const profiles = await this.profilesService.getProfiles()
|
||||
let profile = profiles.find(x => x.id === this.config.store.terminal.profile)
|
||||
if (!profile) {
|
||||
profile = profiles.filter(x => x.type === 'local' && x.isBuiltin)[0]
|
||||
}
|
||||
return profile as LocalProfile
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches a new terminal with a specific shell and CWD
|
||||
* @param pause Wait for a keypress when the shell exits
|
||||
*/
|
||||
async openTab (profile?: Profile|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> {
|
||||
async openTab (profile?: LocalProfile|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> {
|
||||
if (!profile) {
|
||||
profile = await this.getProfileByID(this.config.store.terminal.profile)
|
||||
if (!profile) {
|
||||
profile = (await this.getProfiles({ includeHidden: true }))[0]
|
||||
}
|
||||
profile = await this.getDefaultProfile()
|
||||
}
|
||||
|
||||
cwd = cwd ?? profile.sessionOptions.cwd
|
||||
cwd = cwd ?? profile.options.cwd
|
||||
|
||||
if (cwd && !fs.existsSync(cwd)) {
|
||||
console.warn('Ignoring non-existent CWD:', cwd)
|
||||
cwd = null
|
||||
}
|
||||
|
||||
if (!cwd) {
|
||||
if (!this.config.store.terminal.alwaysUseWorkingDirectory) {
|
||||
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
|
||||
cwd = await this.app.activeTab.session.getWorkingDirectory()
|
||||
}
|
||||
if (this.app.activeTab instanceof SplitTabComponent) {
|
||||
const focusedTab = this.app.activeTab.getFocusedTab()
|
||||
|
||||
if (focusedTab instanceof TerminalTabComponent && focusedTab.session) {
|
||||
cwd = await focusedTab.session.getWorkingDirectory()
|
||||
}
|
||||
}
|
||||
}
|
||||
cwd = cwd ?? this.config.store.terminal.workingDirectory
|
||||
}
|
||||
|
||||
this.logger.info(`Starting profile ${profile.name}`, profile)
|
||||
const sessionOptions = {
|
||||
...profile.sessionOptions,
|
||||
const options = {
|
||||
...profile.options,
|
||||
pauseAfterExit: pause,
|
||||
cwd: cwd ?? undefined,
|
||||
}
|
||||
|
||||
const tab = this.openTabWithOptions(sessionOptions)
|
||||
if (profile.color) {
|
||||
(this.app.getParentTab(tab) ?? tab).color = profile.color
|
||||
}
|
||||
if (profile.disableDynamicTitle) {
|
||||
tab.enableDynamicTitle = false
|
||||
tab.setTitle(profile.name)
|
||||
}
|
||||
return tab
|
||||
}
|
||||
|
||||
optionsFromShell (shell: Shell): SessionOptions {
|
||||
return {
|
||||
command: shell.command,
|
||||
args: shell.args ?? [],
|
||||
env: shell.env,
|
||||
}
|
||||
return (await this.profilesService.openNewTabForProfile({
|
||||
...profile,
|
||||
options,
|
||||
})) as TerminalTabComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a terminal with custom session options
|
||||
*/
|
||||
openTabWithOptions (sessionOptions: SessionOptions): TerminalTabComponent {
|
||||
if (sessionOptions.runAsAdministrator && this.uac.isAvailable) {
|
||||
sessionOptions = this.uac.patchSessionOptionsForUAC(sessionOptions)
|
||||
}
|
||||
this.logger.info('Using session options:', sessionOptions)
|
||||
|
||||
return this.app.openNewTab(
|
||||
TerminalTabComponent,
|
||||
{ sessionOptions }
|
||||
) as TerminalTabComponent
|
||||
}
|
||||
|
||||
private async getShells (): Promise<Shell[]> {
|
||||
const shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
|
||||
return shellLists.reduce((a, b) => a.concat(b), [])
|
||||
}
|
||||
|
||||
private async reloadShells () {
|
||||
this.shells = new AsyncSubject<Shell[]>()
|
||||
const shells = await this.getShells()
|
||||
this.logger.debug('Shells list:', shells)
|
||||
this.shells.next(shells)
|
||||
this.shells.complete()
|
||||
return this.app.openNewTab({
|
||||
type: TerminalTabComponent,
|
||||
inputs: { sessionOptions },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import * as psNode from 'ps-node'
|
||||
import * as fs from 'mz/fs'
|
||||
import * as os from 'os'
|
||||
import { Injector } from '@angular/core'
|
||||
import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA } from 'tabby-core'
|
||||
import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA, LogService } from 'tabby-core'
|
||||
import { BaseSession } from 'tabby-terminal'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { getWorkingDirectoryFromPID } from 'native-process-working-directory'
|
||||
@@ -97,7 +97,7 @@ export class Session extends BaseSession {
|
||||
private bootstrapData: BootstrapData
|
||||
|
||||
constructor (injector: Injector) {
|
||||
super()
|
||||
super(injector.get(LogService).create('local'))
|
||||
this.config = injector.get(ConfigService)
|
||||
this.hostApp = injector.get(HostAppService)
|
||||
this.bootstrapData = injector.get(BOOTSTRAP_DATA)
|
||||
@@ -122,7 +122,13 @@ export class Session extends BaseSession {
|
||||
...this.config.store.terminal.environment || {},
|
||||
}
|
||||
|
||||
if (this.hostApp.platform === Platform.Windows) {
|
||||
if (this.hostApp.platform === Platform.Windows && this.config.store.terminal.setComSpec) {
|
||||
for (const k of Object.keys(env)) {
|
||||
if (k.toUpperCase() === 'COMSPEC') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete env[k]
|
||||
}
|
||||
}
|
||||
env.COMSPEC = this.bootstrapData.executable
|
||||
}
|
||||
|
||||
@@ -258,7 +264,8 @@ export class Session extends BaseSession {
|
||||
return new Promise<ChildProcess[]>((resolve, reject) => {
|
||||
psNode.lookup({ ppid: this.truePID }, (err, processes) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve(processes as ChildProcess[])
|
||||
})
|
||||
@@ -302,7 +309,7 @@ export class Session extends BaseSession {
|
||||
try {
|
||||
cwd = getWorkingDirectoryFromPID(this.truePID)
|
||||
} catch (exc) {
|
||||
console.error(exc)
|
||||
console.info('Could not read working directory:', exc)
|
||||
}
|
||||
|
||||
try {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HostAppService, Platform } from 'tabby-core'
|
||||
import { SettingsTabProvider } from 'tabby-settings'
|
||||
|
||||
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
|
||||
@@ -10,7 +11,13 @@ export class ShellSettingsTabProvider extends SettingsTabProvider {
|
||||
icon = 'list-ul'
|
||||
title = 'Shell'
|
||||
|
||||
constructor (private hostApp: HostAppService) {
|
||||
super()
|
||||
}
|
||||
|
||||
getComponentType (): any {
|
||||
return ShellSettingsTabComponent
|
||||
if (this.hostApp.platform === Platform.Windows) {
|
||||
return ShellSettingsTabComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,25 +0,0 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ConfigService } from 'tabby-core'
|
||||
|
||||
import { ShellProvider, Shell } from '../api'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class CustomShellProvider extends ShellProvider {
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async provide (): Promise<Shell[]> {
|
||||
const args = this.config.store.terminal.customShell.split(' ')
|
||||
return [{
|
||||
id: 'custom',
|
||||
name: 'Custom shell',
|
||||
command: args[0],
|
||||
args: args.slice(1),
|
||||
env: {},
|
||||
}]
|
||||
}
|
||||
}
|
@@ -21,7 +21,7 @@ export class MacOSDefaultShellProvider extends ShellProvider {
|
||||
}
|
||||
return [{
|
||||
id: 'default',
|
||||
name: 'User default',
|
||||
name: 'OS default',
|
||||
command: await this.getDefaultShellCached(),
|
||||
args: ['--login'],
|
||||
hidden: true,
|
||||
|
@@ -25,6 +25,7 @@ export class POSIXShellsProvider extends ShellProvider {
|
||||
.map(x => ({
|
||||
id: slugify(x),
|
||||
name: x.split('/')[2],
|
||||
icon: 'fas fa-terminal',
|
||||
command: x,
|
||||
args: ['-l'],
|
||||
env: {},
|
||||
|
68
tabby-local/src/shells/vs.ts
Normal file
68
tabby-local/src/shells/vs.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs/promises'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HostAppService, Platform } from 'tabby-core'
|
||||
|
||||
import { ShellProvider, Shell } from '../api'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class VSDevToolsProvider extends ShellProvider {
|
||||
constructor (
|
||||
private hostApp: HostAppService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async provide (): Promise<Shell[]> {
|
||||
if (this.hostApp.platform !== Platform.Windows) {
|
||||
return []
|
||||
}
|
||||
|
||||
const parentPath = path.join(process.env['programfiles(x86)'] ?? 'C:\\Program Files (x86', 'Microsoft Visual Studio')
|
||||
|
||||
try {
|
||||
await fs.stat(parentPath)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
const result: Shell[] = []
|
||||
for (const version of await fs.readdir(parentPath)) {
|
||||
const bat = path.join(parentPath, version, 'Community\\Common7\\Tools\\VsDevCmd.bat')
|
||||
try {
|
||||
await fs.stat(bat)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
result.push({
|
||||
id: `vs-cmd-${version}`,
|
||||
name: `Developer Prompt for VS ${version}`,
|
||||
command: 'cmd.exe',
|
||||
args: ['/k', bat],
|
||||
icon: require('../icons/vs.svg'),
|
||||
env: {},
|
||||
})
|
||||
}
|
||||
return result
|
||||
|
||||
// return [
|
||||
// {
|
||||
// id: 'cmderps',
|
||||
// name: 'Cmder PowerShell',
|
||||
// command: 'powershell.exe',
|
||||
// args: [
|
||||
// '-ExecutionPolicy',
|
||||
// 'Bypass',
|
||||
// '-nologo',
|
||||
// '-noprofile',
|
||||
// '-noexit',
|
||||
// '-command',
|
||||
// `Invoke-Expression '. ''${path.join(process.env.CMDER_ROOT, 'vendor', 'profile.ps1')}'''`,
|
||||
// ],
|
||||
// icon: require('../icons/cmder-powershell.svg'),
|
||||
// env: {},
|
||||
// },
|
||||
// ]
|
||||
}
|
||||
}
|
@@ -39,7 +39,7 @@ export class WindowsDefaultShellProvider extends ShellProvider {
|
||||
return [{
|
||||
...shell,
|
||||
id: 'default',
|
||||
name: `Default (${shell.name})`,
|
||||
name: `OS default (${shell.name})`,
|
||||
hidden: true,
|
||||
env: {},
|
||||
}]
|
||||
|
@@ -1,14 +1,17 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService, MenuItemOptions } from 'tabby-core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent } from 'tabby-core'
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { UACService } from './services/uac.service'
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
import { LocalProfile } from './api'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
private ngbModal: NgbModal,
|
||||
private notifications: NotificationsService,
|
||||
) {
|
||||
super()
|
||||
@@ -22,15 +25,22 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
||||
{
|
||||
label: 'Save as profile',
|
||||
click: async () => {
|
||||
const modal = this.ngbModal.open(PromptModalComponent)
|
||||
modal.componentInstance.prompt = 'New profile name'
|
||||
const name = (await modal.result)?.name
|
||||
if (!name) {
|
||||
return
|
||||
}
|
||||
const profile = {
|
||||
sessionOptions: {
|
||||
options: {
|
||||
...tab.sessionOptions,
|
||||
cwd: await tab.session?.getWorkingDirectory() ?? tab.sessionOptions.cwd,
|
||||
},
|
||||
name: tab.sessionOptions.command,
|
||||
name,
|
||||
type: 'local',
|
||||
}
|
||||
this.config.store.terminal.profiles = [
|
||||
...this.config.store.terminal.profiles,
|
||||
this.config.store.profiles = [
|
||||
...this.config.store.profiles,
|
||||
profile,
|
||||
]
|
||||
this.config.save()
|
||||
@@ -50,6 +60,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
constructor (
|
||||
public config: ConfigService,
|
||||
private profilesService: ProfilesService,
|
||||
private terminalService: TerminalService,
|
||||
private uac: UACService,
|
||||
) {
|
||||
@@ -57,7 +68,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
}
|
||||
|
||||
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
|
||||
const profiles = await this.terminalService.getProfiles()
|
||||
const profiles = (await this.profilesService.getProfiles()).filter(x => x.type === 'local') as LocalProfile[]
|
||||
|
||||
const items: MenuItemOptions[] = [
|
||||
{
|
||||
@@ -71,9 +82,9 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
submenu: profiles.map(profile => ({
|
||||
label: profile.name,
|
||||
click: async () => {
|
||||
let workingDirectory = this.config.store.terminal.workingDirectory
|
||||
if (this.config.store.terminal.alwaysUseWorkingDirectory !== true && tab instanceof TerminalTabComponent) {
|
||||
workingDirectory = await tab.session?.getWorkingDirectory()
|
||||
let workingDirectory = profile.options.cwd
|
||||
if (!workingDirectory && tab instanceof TerminalTabComponent) {
|
||||
workingDirectory = await tab.session?.getWorkingDirectory() ?? undefined
|
||||
}
|
||||
await this.terminalService.openTab(profile, workingDirectory)
|
||||
},
|
||||
@@ -88,7 +99,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
label: profile.name,
|
||||
click: () => {
|
||||
this.terminalService.openTabWithOptions({
|
||||
...profile.sessionOptions,
|
||||
...profile.options,
|
||||
runAsAdministrator: true,
|
||||
})
|
||||
},
|
||||
|
@@ -371,11 +371,6 @@ side-channel@^1.0.3:
|
||||
get-intrinsic "^1.0.2"
|
||||
object-inspect "^1.9.0"
|
||||
|
||||
slugify@^1.5.3:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.5.3.tgz#36e009864f5476bfd5db681222643d92339c890d"
|
||||
integrity sha512-/HkjRdwPY3yHJReXu38NiusZw2+LLE2SrhkWJtmlPDB1fqFSvioYj62NkPcrKiNCgRLeGcGK7QBvr1iQwybeXw==
|
||||
|
||||
string.prototype.codepointat@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
|
||||
@@ -441,9 +436,9 @@ unbox-primitive@^1.0.0:
|
||||
which-boxed-primitive "^1.0.2"
|
||||
|
||||
utils-decorators@^1.8.3:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/utils-decorators/-/utils-decorators-1.8.3.tgz#7ec9c2b4a943658de34cb2533bf2fd9c4b1cd61b"
|
||||
integrity sha512-QtoRQikWeYtMZsT5ChOz5HNzYxLGCEBUELQ1J8+WIuSQo+1D2bPwIY08DKzw88YSl2HXes60RxfafYhgXnlVJA==
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/utils-decorators/-/utils-decorators-1.10.0.tgz#eb9208ccbb7fbb7488d5d04b2611df62c2fcaf4d"
|
||||
integrity sha512-wlNRoPCFdxSReLfmhNqkZsg8FqsKu9d5trdSELxBZCtmK4KPtSidxRg24+bpZQjEBBF0hUIQEFz2uM7sBDVG2Q==
|
||||
dependencies:
|
||||
tinyqueue "^2.0.3"
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tabby-plugin-manager",
|
||||
"version": "1.0.144",
|
||||
"version": "1.0.145",
|
||||
"description": "Tabby's plugin manager",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
|
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { BehaviorSubject, Observable } from 'rxjs'
|
||||
import { debounceTime, distinctUntilChanged, first, tap, flatMap, map } from 'rxjs/operators'
|
||||
import { BehaviorSubject, Observable, debounceTime, distinctUntilChanged, first, tap, flatMap, map } from 'rxjs'
|
||||
import semverGt from 'semver/functions/gt'
|
||||
|
||||
import { Component, Input } from '@angular/core'
|
||||
@@ -51,7 +50,7 @@ export class PluginsSettingsTabComponent {
|
||||
return plugins
|
||||
})).subscribe(available => {
|
||||
for (const plugin of this.pluginManager.installedPlugins) {
|
||||
this.knownUpgrades[plugin.name] = available.find(x => x.name === plugin.name && semverGt(x.version, plugin.version)) || null
|
||||
this.knownUpgrades[plugin.name] = available.find(x => x.name === plugin.name && semverGt(x.version, plugin.version)) ?? null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios'
|
||||
import { Observable, from, forkJoin } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
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'
|
||||
|
||||
@@ -56,6 +56,17 @@ export class PluginManagerService {
|
||||
),
|
||||
map(plugins => plugins.filter(x => x.packageName.startsWith(namePrefix))),
|
||||
map(plugins => plugins.filter(x => !BLACKLIST.includes(x.packageName))),
|
||||
map(plugins => {
|
||||
const mapping: Record<string, PluginInfo[]> = {}
|
||||
for (const p of plugins) {
|
||||
mapping[p.name] ??= []
|
||||
mapping[p.name].push(p)
|
||||
}
|
||||
return Object.values(mapping).map(list => {
|
||||
list.sort((a, b) => -semverCompare(a.version, b.version))
|
||||
return list[0]
|
||||
})
|
||||
}),
|
||||
map(plugins => plugins.sort((a, b) => a.name.localeCompare(b.name))),
|
||||
)
|
||||
}
|
||||
|
@@ -3,9 +3,9 @@
|
||||
|
||||
|
||||
"@types/semver@^7.1.0":
|
||||
version "7.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.6.tgz#e9831776f4512a7ba6da53e71c26e5fb67882d63"
|
||||
integrity sha512-0caWDWmpCp0uifxFh+FaqK3CuZ2SkRR/ZRxAV5+zNdC3QVUi6wyOJnefhPvtNt8NQWXB5OA93BUvZsXpWat2Xw==
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.7.tgz#b9eb89d7dfa70d5d1ce525bc1411a35347f533a3"
|
||||
integrity sha512-4g1jrL98mdOIwSOUh6LTlB0Cs9I0dQPwINUhBg7C6pN4HLr8GS8xsksJxilW6S6dQHVi2K/o+lQuQcg7LroCnw==
|
||||
|
||||
axios@^0.21.1:
|
||||
version "0.21.1"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tabby-serial",
|
||||
"version": "1.0.144",
|
||||
"description": "Serial connection manager for Tabby",
|
||||
"version": "1.0.145",
|
||||
"description": "Serial connections for Tabby",
|
||||
"keywords": [
|
||||
"tabby-builtin-plugin"
|
||||
],
|
||||
@@ -18,11 +18,7 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "14.14.14",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"binstring": "^0.2.1",
|
||||
"buffer-replace": "^1.0.0",
|
||||
"cli-spinner": "^0.2.10",
|
||||
"hexer": "^1.5.0"
|
||||
"ansi-colors": "^4.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "^9.1.9",
|
||||
|
@@ -1,40 +1,25 @@
|
||||
import hexdump from 'hexer'
|
||||
import colors from 'ansi-colors'
|
||||
import binstring from 'binstring'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
import bufferReplace from 'buffer-replace'
|
||||
import { BaseSession } from 'tabby-terminal'
|
||||
import { SerialPort } from 'serialport'
|
||||
import { Logger } from 'tabby-core'
|
||||
import { Subject, Observable, interval } from 'rxjs'
|
||||
import { debounce } from 'rxjs/operators'
|
||||
import { ReadLine, createInterface as createReadline, clearLine } from 'readline'
|
||||
import { PassThrough, Readable, Writable } from 'stream'
|
||||
import SerialPort from 'serialport'
|
||||
import { LogService, NotificationsService, Profile } from 'tabby-core'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { Injector, NgZone } from '@angular/core'
|
||||
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
||||
|
||||
export interface LoginScript {
|
||||
expect: string
|
||||
send: string
|
||||
isRegex?: boolean
|
||||
optional?: boolean
|
||||
export interface SerialProfile extends Profile {
|
||||
options: SerialProfileOptions
|
||||
}
|
||||
|
||||
export interface SerialConnection {
|
||||
name: string
|
||||
export interface SerialProfileOptions extends StreamProcessingOptions, LoginScriptsOptions {
|
||||
port: string
|
||||
baudrate: number
|
||||
databits: number
|
||||
stopbits: number
|
||||
parity: string
|
||||
rtscts: boolean
|
||||
xon: boolean
|
||||
xoff: boolean
|
||||
xany: boolean
|
||||
scripts?: LoginScript[]
|
||||
baudrate?: number
|
||||
databits?: number
|
||||
stopbits?: number
|
||||
parity?: string
|
||||
rtscts?: boolean
|
||||
xon?: boolean
|
||||
xoff?: boolean
|
||||
xany?: boolean
|
||||
color?: string
|
||||
inputMode?: InputMode
|
||||
inputNewlines?: NewlineMode
|
||||
outputMode?: OutputMode
|
||||
outputNewlines?: NewlineMode
|
||||
}
|
||||
|
||||
export const BAUD_RATES = [
|
||||
@@ -46,49 +31,78 @@ export interface SerialPortInfo {
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type InputMode = null | 'readline' | 'readline-hex' // eslint-disable-line @typescript-eslint/no-type-alias
|
||||
export type OutputMode = null | 'hex' // eslint-disable-line @typescript-eslint/no-type-alias
|
||||
export type NewlineMode = null | 'cr' | 'lf' | 'crlf' // eslint-disable-line @typescript-eslint/no-type-alias
|
||||
|
||||
export class SerialSession extends BaseSession {
|
||||
scripts?: LoginScript[]
|
||||
serial: SerialPort
|
||||
logger: Logger
|
||||
|
||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||
private serviceMessage = new Subject<string>()
|
||||
private inputReadline: ReadLine
|
||||
private inputPromptVisible = true
|
||||
private inputReadlineInStream: Readable & Writable
|
||||
private inputReadlineOutStream: Readable & Writable
|
||||
private streamProcessor: TerminalStreamProcessor
|
||||
private zone: NgZone
|
||||
private notifications: NotificationsService
|
||||
|
||||
constructor (public connection: SerialConnection) {
|
||||
super()
|
||||
this.scripts = connection.scripts ?? []
|
||||
constructor (injector: Injector, public profile: SerialProfile) {
|
||||
super(injector.get(LogService).create(`serial-${profile.options.port}`))
|
||||
this.zone = injector.get(NgZone)
|
||||
this.notifications = injector.get(NotificationsService)
|
||||
|
||||
this.inputReadlineInStream = new PassThrough()
|
||||
this.inputReadlineOutStream = new PassThrough()
|
||||
this.inputReadline = createReadline({
|
||||
input: this.inputReadlineInStream,
|
||||
output: this.inputReadlineOutStream,
|
||||
terminal: true,
|
||||
prompt: this.connection.inputMode === 'readline-hex' ? 'hex> ' : '> ',
|
||||
} as any)
|
||||
this.inputReadlineOutStream.on('data', data => {
|
||||
this.emitOutput(Buffer.from(data))
|
||||
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
||||
this.streamProcessor.outputToSession$.subscribe(data => {
|
||||
this.serial?.write(data.toString())
|
||||
})
|
||||
this.inputReadline.on('line', line => {
|
||||
this.onInput(Buffer.from(line + '\n'))
|
||||
this.resetInputPrompt()
|
||||
this.streamProcessor.outputToTerminal$.subscribe(data => {
|
||||
this.emitOutput(data)
|
||||
this.loginScriptProcessor?.feedFromSession(data)
|
||||
})
|
||||
this.output$.pipe(debounce(() => interval(500))).subscribe(() => this.onOutputSettled())
|
||||
|
||||
this.setLoginScriptsOptions(profile.options)
|
||||
}
|
||||
|
||||
async start (): Promise<void> {
|
||||
this.serial = new SerialPort(this.profile.options.port, {
|
||||
autoOpen: false,
|
||||
baudRate: parseInt(this.profile.options.baudrate as any),
|
||||
dataBits: this.profile.options.databits ?? 8,
|
||||
stopBits: this.profile.options.stopbits ?? 1,
|
||||
parity: this.profile.options.parity ?? 'none',
|
||||
rtscts: this.profile.options.rtscts ?? false,
|
||||
xon: this.profile.options.xon ?? false,
|
||||
xoff: this.profile.options.xoff ?? false,
|
||||
xany: this.profile.options.xany ?? false,
|
||||
})
|
||||
let connected = false
|
||||
await new Promise(async (resolve, reject) => {
|
||||
this.serial.on('open', () => {
|
||||
connected = true
|
||||
this.zone.run(resolve)
|
||||
})
|
||||
this.serial.on('error', error => {
|
||||
this.zone.run(() => {
|
||||
if (connected) {
|
||||
this.notifications.error(error.toString())
|
||||
} else {
|
||||
reject(error)
|
||||
}
|
||||
this.destroy()
|
||||
})
|
||||
})
|
||||
this.serial.on('close', () => {
|
||||
this.emitServiceMessage('Port closed')
|
||||
this.destroy()
|
||||
})
|
||||
|
||||
try {
|
||||
this.serial.open()
|
||||
} catch (e) {
|
||||
this.notifications.error(e.message)
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
|
||||
this.open = true
|
||||
setTimeout(() => this.streamProcessor.start())
|
||||
|
||||
this.serial.on('readable', () => {
|
||||
this.onOutput(this.serial.read())
|
||||
this.streamProcessor.feedFromSession(this.serial.read())
|
||||
})
|
||||
|
||||
this.serial.on('end', () => {
|
||||
@@ -98,26 +112,22 @@ export class SerialSession extends BaseSession {
|
||||
}
|
||||
})
|
||||
|
||||
this.executeUnconditionalScripts()
|
||||
this.loginScriptProcessor?.executeUnconditionalScripts()
|
||||
}
|
||||
|
||||
write (data: Buffer): void {
|
||||
if (this.connection.inputMode?.startsWith('readline')) {
|
||||
this.inputReadlineInStream.write(data)
|
||||
} else {
|
||||
this.onInput(data)
|
||||
}
|
||||
this.streamProcessor.feedFromTerminal(data)
|
||||
}
|
||||
|
||||
async destroy (): Promise<void> {
|
||||
this.streamProcessor.close()
|
||||
this.serviceMessage.complete()
|
||||
this.inputReadline.close()
|
||||
await super.destroy()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||
resize (_, __) {
|
||||
this.inputReadlineOutStream.emit('resize')
|
||||
this.streamProcessor.resize()
|
||||
}
|
||||
|
||||
kill (_?: string): void {
|
||||
@@ -144,135 +154,4 @@ export class SerialSession extends BaseSession {
|
||||
async getWorkingDirectory (): Promise<string|null> {
|
||||
return null
|
||||
}
|
||||
|
||||
private replaceNewlines (data: Buffer, mode?: NewlineMode): Buffer {
|
||||
if (!mode) {
|
||||
return data
|
||||
}
|
||||
data = bufferReplace(data, '\r\n', '\n')
|
||||
data = bufferReplace(data, '\r', '\n')
|
||||
const replacement = {
|
||||
strip: '',
|
||||
cr: '\r',
|
||||
lf: '\n',
|
||||
crlf: '\r\n',
|
||||
}[mode]
|
||||
return bufferReplace(data, '\n', replacement)
|
||||
}
|
||||
|
||||
private onInput (data: Buffer) {
|
||||
if (this.connection.inputMode === 'readline-hex') {
|
||||
const tokens = data.toString().split(/\s/g)
|
||||
data = Buffer.concat(tokens.filter(t => !!t).map(t => {
|
||||
if (t.startsWith('0x')) {
|
||||
t = t.substring(2)
|
||||
}
|
||||
return binstring(t, { 'in': 'hex' })
|
||||
}))
|
||||
}
|
||||
|
||||
data = this.replaceNewlines(data, this.connection.inputNewlines)
|
||||
if (this.serial) {
|
||||
this.serial.write(data.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private onOutputSettled () {
|
||||
if (this.connection.inputMode?.startsWith('readline') && !this.inputPromptVisible) {
|
||||
this.resetInputPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
private resetInputPrompt () {
|
||||
this.emitOutput(Buffer.from('\r\n'))
|
||||
this.inputReadline.prompt(true)
|
||||
this.inputPromptVisible = true
|
||||
}
|
||||
|
||||
private onOutput (data: Buffer) {
|
||||
const dataString = data.toString()
|
||||
|
||||
if (this.connection.inputMode?.startsWith('readline')) {
|
||||
if (this.inputPromptVisible) {
|
||||
clearLine(this.inputReadlineOutStream, 0)
|
||||
this.inputPromptVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
data = this.replaceNewlines(data, this.connection.outputNewlines)
|
||||
|
||||
if (this.connection.outputMode === 'hex') {
|
||||
this.emitOutput(Buffer.concat([
|
||||
Buffer.from('\r\n'),
|
||||
Buffer.from(hexdump(data, {
|
||||
group: 1,
|
||||
gutter: 4,
|
||||
divide: colors.gray(' | '),
|
||||
emptyHuman: colors.gray('╳'),
|
||||
}).replace(/\n/g, '\r\n')),
|
||||
Buffer.from('\r\n\n'),
|
||||
]))
|
||||
} else {
|
||||
this.emitOutput(data)
|
||||
}
|
||||
|
||||
if (this.scripts) {
|
||||
let found = false
|
||||
for (const script of this.scripts) {
|
||||
let match = false
|
||||
let cmd = ''
|
||||
if (script.isRegex) {
|
||||
const re = new RegExp(script.expect, 'g')
|
||||
if (re.test(dataString)) {
|
||||
cmd = dataString.replace(re, script.send)
|
||||
match = true
|
||||
found = true
|
||||
}
|
||||
} else {
|
||||
if (dataString.includes(script.expect)) {
|
||||
cmd = script.send
|
||||
match = true
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
this.logger.info('Executing script: "' + cmd + '"')
|
||||
this.serial.write(cmd + '\n')
|
||||
this.scripts = this.scripts.filter(x => x !== script)
|
||||
} else {
|
||||
if (script.optional) {
|
||||
this.logger.debug('Skip optional script: ' + script.expect)
|
||||
found = true
|
||||
this.scripts = this.scripts.filter(x => x !== script)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
this.executeUnconditionalScripts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private executeUnconditionalScripts () {
|
||||
if (this.scripts) {
|
||||
for (const script of this.scripts) {
|
||||
if (!script.expect) {
|
||||
console.log('Executing script:', script.send)
|
||||
this.serial.write(script.send + '\n')
|
||||
this.scripts = this.scripts.filter(x => x !== script)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SerialConnectionGroup {
|
||||
name: string
|
||||
connections: SerialConnection[]
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Injectable, Injector } from '@angular/core'
|
||||
import { HotkeysService, ToolbarButtonProvider, ToolbarButton } from 'tabby-core'
|
||||
import { SerialService } from './services/serial.service'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class ButtonProvider extends ToolbarButtonProvider {
|
||||
constructor (
|
||||
private injector: Injector,
|
||||
hotkeys: HotkeysService,
|
||||
) {
|
||||
super()
|
||||
hotkeys.matchedHotkey.subscribe(async (hotkey: string) => {
|
||||
if (hotkey === 'serial') {
|
||||
this.activate()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
activate () {
|
||||
this.injector.get(SerialService).showConnectionSelector()
|
||||
}
|
||||
|
||||
provide (): ToolbarButton[] {
|
||||
return [{
|
||||
icon: require('./icons/serial.svg'),
|
||||
weight: 5,
|
||||
title: 'Serial connections',
|
||||
touchBarNSImage: 'NSTouchBarOpenInBrowserTemplate',
|
||||
click: () => {
|
||||
this.activate()
|
||||
},
|
||||
}]
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { CLIHandler, CLIEvent, ConfigService } from 'tabby-core'
|
||||
import { SerialService } from './services/serial.service'
|
||||
|
||||
@Injectable()
|
||||
export class SerialCLIHandler extends CLIHandler {
|
||||
firstMatchOnly = true
|
||||
priority = 0
|
||||
|
||||
constructor (
|
||||
private serial: SerialService,
|
||||
private config: ConfigService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async handle (event: CLIEvent): Promise<boolean> {
|
||||
const op = event.argv._[0]
|
||||
|
||||
if (op === 'connect-serial') {
|
||||
const connection = this.config.store.serial.connections.find(x => x.name === event.argv.connectionName)
|
||||
if (connection) {
|
||||
this.serial.connect(connection)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user