mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-25 10:41:51 +00:00
Compare commits
92 Commits
v1.0.0-alp
...
v1.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0755ff291d | ||
![]() |
c0c2b693f3 | ||
![]() |
23dabca2ab | ||
![]() |
98a5a95bec | ||
![]() |
5045c4c82a | ||
![]() |
feb4c5bcb6 | ||
![]() |
de29e34363 | ||
![]() |
0fe2de591a | ||
![]() |
9bee253dd0 | ||
![]() |
a26b38f5ae | ||
![]() |
9312db1fc6 | ||
![]() |
932ed9b8f2 | ||
![]() |
49b90f15bc | ||
![]() |
5f5772501b | ||
![]() |
ed0bfb39f3 | ||
![]() |
60ce10d1e3 | ||
![]() |
e64126af37 | ||
![]() |
19565fba2c | ||
![]() |
f39443b0ff | ||
![]() |
5dde5479d8 | ||
![]() |
3d29b99ddf | ||
![]() |
4ba7cf106f | ||
![]() |
23c7acde51 | ||
![]() |
c41efbd62b | ||
![]() |
1643ca2bd5 | ||
![]() |
b640abd85d | ||
![]() |
dcf9817075 | ||
![]() |
6f8f83d178 | ||
![]() |
475c4f91be | ||
![]() |
29a6fb60de | ||
![]() |
42007f4fef | ||
![]() |
92c0df7629 | ||
![]() |
e70cb25180 | ||
![]() |
980834df6f | ||
![]() |
50968508df | ||
![]() |
8ee93297be | ||
![]() |
dc9b2553ae | ||
![]() |
a6f7f7aa05 | ||
![]() |
c087a969d6 | ||
![]() |
f4eb03fee0 | ||
![]() |
deb4b963cd | ||
![]() |
9834b27b8d | ||
![]() |
fc060acd88 | ||
![]() |
709ffadc7c | ||
![]() |
536d9537ff | ||
![]() |
94217f0b01 | ||
![]() |
80762e92d6 | ||
![]() |
e6ef21fa9d | ||
![]() |
da89560d6b | ||
![]() |
c0c2373ed6 | ||
![]() |
f2a8eb92a1 | ||
![]() |
48e8ffd729 | ||
![]() |
7327a7008c | ||
![]() |
856c7e7e9e | ||
![]() |
353a4da083 | ||
![]() |
3068c27fd6 | ||
![]() |
7de0bd95b9 | ||
![]() |
904828c3e3 | ||
![]() |
63757f7726 | ||
![]() |
05f16f1719 | ||
![]() |
78e115b698 | ||
![]() |
55b53ed5b7 | ||
![]() |
1fa7b40913 | ||
![]() |
5b7ded9097 | ||
![]() |
06b60b86f2 | ||
![]() |
052c941275 | ||
![]() |
72899b0cf2 | ||
![]() |
342316f5a5 | ||
![]() |
c215faaeb8 | ||
![]() |
c4c342bd0a | ||
![]() |
4472a033a1 | ||
![]() |
093876a445 | ||
![]() |
359e0926cb | ||
![]() |
0e9282e079 | ||
![]() |
5511f4d7cb | ||
![]() |
3ae76e66ed | ||
![]() |
42059e3bc6 | ||
![]() |
c093b204d7 | ||
![]() |
8a8f89b386 | ||
![]() |
b109fb8766 | ||
![]() |
f58cf469ed | ||
![]() |
57227ae6ce | ||
![]() |
b670fa843a | ||
![]() |
c41e551c58 | ||
![]() |
60b02b17e4 | ||
![]() |
bf8bb7ee80 | ||
![]() |
1e1d48a5f8 | ||
![]() |
77b55a003c | ||
![]() |
165ab1cfbf | ||
![]() |
d4840bafaf | ||
![]() |
9badd88c80 | ||
![]() |
7a24c9aa3a |
108
.gitlab-ci.yml
108
.gitlab-ci.yml
@@ -1,108 +0,0 @@
|
|||||||
cache:
|
|
||||||
untracked: true
|
|
||||||
key: "$CI_BUILD_REF_NAME"
|
|
||||||
paths:
|
|
||||||
- app/node_modules
|
|
||||||
- node_modules
|
|
||||||
- typings
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- Build
|
|
||||||
- Test
|
|
||||||
- Package
|
|
||||||
- Upload
|
|
||||||
|
|
||||||
Build:
|
|
||||||
stage: Build
|
|
||||||
script:
|
|
||||||
- npm prune
|
|
||||||
- npm install
|
|
||||||
- cd app; npm prune && npm install; cd ..
|
|
||||||
- ./node_modules/.bin/typings install
|
|
||||||
tags:
|
|
||||||
- Linux
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
- typings
|
|
||||||
- app
|
|
||||||
|
|
||||||
Test:
|
|
||||||
stage: Test
|
|
||||||
dependencies:
|
|
||||||
- Build
|
|
||||||
script:
|
|
||||||
- apt-get install -y xvfb libxtst6 libxss1 libgconf2-4 libnss3 libasound2
|
|
||||||
- xvfb-run -a make coverage
|
|
||||||
tags:
|
|
||||||
- Linux
|
|
||||||
|
|
||||||
Windows package:
|
|
||||||
stage: Package
|
|
||||||
dependencies:
|
|
||||||
- Build
|
|
||||||
script:
|
|
||||||
- call npm install
|
|
||||||
- call npm install webpack # regenerate the .cmd launcher
|
|
||||||
- cd app
|
|
||||||
- call npm install
|
|
||||||
- cd ..
|
|
||||||
- call ./node_modules/.bin/webpack.cmd --progress
|
|
||||||
- call make package-windows
|
|
||||||
- call copy dist\Elements-Electron.exe Elements-Windows-%CI_BUILD_REF_NAME%.exe
|
|
||||||
artifacts:
|
|
||||||
name: Elements-Windows-%CI_BUILD_REF_NAME%
|
|
||||||
paths:
|
|
||||||
- Elements-Windows-%CI_BUILD_REF_NAME%.exe
|
|
||||||
tags:
|
|
||||||
- Windows
|
|
||||||
|
|
||||||
macOS package:
|
|
||||||
stage: Package
|
|
||||||
dependencies:
|
|
||||||
- Build
|
|
||||||
script:
|
|
||||||
- npm install
|
|
||||||
- rm -rf node_modules/electron-macos-sign || true
|
|
||||||
- cp -r node_modules/electron-osx-sign node_modules/electron-macos-sign
|
|
||||||
- cd app; npm install; cd ..
|
|
||||||
- ./node_modules/.bin/webpack --progress
|
|
||||||
- security unlock-keychain -p rjvg login.keychain
|
|
||||||
- make package-mac
|
|
||||||
- cp dist/Elements-Electron.pkg ./Elements-macOS-$CI_BUILD_REF_NAME.pkg
|
|
||||||
artifacts:
|
|
||||||
name: Elements-macOS-$CI_BUILD_REF_NAME
|
|
||||||
paths:
|
|
||||||
- Elements-macOS-$CI_BUILD_REF_NAME.pkg
|
|
||||||
tags:
|
|
||||||
- macOS
|
|
||||||
|
|
||||||
Linux package:
|
|
||||||
stage: Package
|
|
||||||
dependencies:
|
|
||||||
- Build
|
|
||||||
script:
|
|
||||||
- npm install
|
|
||||||
- cd app; npm install; cd ..
|
|
||||||
- ./node_modules/.bin/webpack --progress
|
|
||||||
- make build-linux
|
|
||||||
- cp dist/ELEMENTS*.AppImage ./Elements-Linux-$CI_BUILD_REF_NAME.AppImage
|
|
||||||
artifacts:
|
|
||||||
name: Elements-Linux-$CI_BUILD_REF_NAME
|
|
||||||
paths:
|
|
||||||
- Elements-Linux-$CI_BUILD_REF_NAME.AppImage
|
|
||||||
tags:
|
|
||||||
- Linux
|
|
||||||
|
|
||||||
Upload packages:
|
|
||||||
stage: Upload
|
|
||||||
dependencies:
|
|
||||||
- Windows package
|
|
||||||
- macOS package
|
|
||||||
- Linux package
|
|
||||||
script:
|
|
||||||
- scp Elements-Windows-$CI_BUILD_REF_NAME.exe root@cloud.elements.tv:/mnt/elements/www/clients/
|
|
||||||
- scp Elements-macOS-$CI_BUILD_REF_NAME.pkg root@cloud.elements.tv:/mnt/elements/www/clients/
|
|
||||||
- scp Elements-Linux-$CI_BUILD_REF_NAME.AppImage root@cloud.elements.tv:/mnt/elements/www/clients/
|
|
||||||
tags:
|
|
||||||
- Local
|
|
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at e@ajenti.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
12
HACKING.md
12
HACKING.md
@@ -10,12 +10,14 @@ First, install the dependencies:
|
|||||||
|
|
||||||
```
|
```
|
||||||
# macOS/Linux:
|
# macOS/Linux:
|
||||||
npm i
|
sudo npm -g install yarn node-gyp
|
||||||
|
yarn install
|
||||||
./scripts/install-deps.js
|
./scripts/install-deps.js
|
||||||
./scripts/build-native.js
|
./scripts/build-native.js
|
||||||
|
|
||||||
# Windows:
|
# Windows:
|
||||||
npm i
|
npm -g install yarn node-gyp windows-build-tools
|
||||||
|
yarn install
|
||||||
node scripts\install-deps.js
|
node scripts\install-deps.js
|
||||||
node scripts\build-native.js
|
node scripts\build-native.js
|
||||||
```
|
```
|
||||||
@@ -67,7 +69,11 @@ terminus-pluginname
|
|||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
|
|
||||||
The app will load all plugins from the source checkout in the dev mode, and from the user's plugins directory at all times (click `Open Plugins Directory` under `Settings` > `Plugins`).
|
The app will load all plugins from the source checkout in the dev mode, from the user's plugins directory at all times (click `Open Plugins Directory` under `Settings` > `Plugins`) and from the directory specified by the `TERMINUS_PLUGINS` environment var.
|
||||||
|
|
||||||
|
Only modules whose `package.json` file contains a `terminus-plugin` keyword will be loaded.
|
||||||
|
|
||||||
|
If you're currently in your plugin's directory, start Terminus as `TERMINUS_PLUGINS=$(pwd) terminus --debug`
|
||||||
|
|
||||||
A plugin should only provide a default export, which should be a `NgModule` class (or a `NgModuleWithDependencies` where applicable). This module will be injected as a dependency to the app's root module.
|
A plugin should only provide a default export, which should be a `NgModule` class (or a `NgModuleWithDependencies` where applicable). This module will be injected as a dependency to the app's root module.
|
||||||
|
|
||||||
|
14
README.md
14
README.md
@@ -17,7 +17,17 @@
|
|||||||
* Doesn't choke on fast-flowing outputs
|
* Doesn't choke on fast-flowing outputs
|
||||||
* Tab persistence on macOS and Linux
|
* Tab persistence on macOS and Linux
|
||||||
* Proper shell-like experience on Windows including tab completion (thanks, Clink!)
|
* Proper shell-like experience on Windows including tab completion (thanks, Clink!)
|
||||||
* CMD, PowerShell and Bash on Windows support
|
* CMD, PowerShell, Cygwin, Git-Bash and Bash on Windows support
|
||||||
|
* Default Linux style hotkeys for copy (`Ctrl`+`Shift`+`C`) and paste (`Ctrl`+`Shift`+`V`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plugins
|
||||||
|
|
||||||
|
Plugins can be installed directly from the Settings view inside Terminus.
|
||||||
|
|
||||||
|
* [clickable-links](https://github.com/Eugeny/terminus-clickable-links) - makes paths and URLs in the terminal clickable
|
||||||
|
* [theme-hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,4 +35,4 @@
|
|||||||
|
|
||||||
Pull requests and plugins are welcome! Publish your plugin on NPM with a `terminus-plugin` keyword to make them appear in the Plugin Manager.
|
Pull requests and plugins are welcome! Publish your plugin on NPM with a `terminus-plugin` keyword to make them appear in the Plugin Manager.
|
||||||
|
|
||||||
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) for a very plugin development tutorial!
|
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) for a very brief plugin development tutorial!
|
||||||
|
105
app/main.js
105
app/main.js
@@ -1,13 +1,15 @@
|
|||||||
if (process.platform == 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
if (process.platform == 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
require('electron-debug')({enabled: true, showDevTools: (process.argv.indexOf('--debug') != -1) ? 'undocked' : false})
|
if (process.argv.indexOf('--debug') !== -1) {
|
||||||
|
require('electron-debug')({enabled: true, showDevTools: 'undocked'})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let app = electron.app
|
let app = electron.app
|
||||||
|
|
||||||
let secondInstance = app.makeSingleInstance((argv) => {
|
let secondInstance = app.makeSingleInstance((argv, cwd) => {
|
||||||
app.window.webContents.send('host:second-instance')
|
app.window.webContents.send('host:second-instance', argv, cwd)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (secondInstance) {
|
if (secondInstance) {
|
||||||
@@ -20,7 +22,6 @@ const yaml = require('js-yaml')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const Config = require('electron-config')
|
const Config = require('electron-config')
|
||||||
require('electron-debug')({enabled: true, showDevTools: process.argv.indexOf('--debug') != -1})
|
|
||||||
let windowConfig = new Config({name: 'window'})
|
let windowConfig = new Config({name: 'window'})
|
||||||
|
|
||||||
|
|
||||||
@@ -29,28 +30,18 @@ if (!process.env.TERMINUS_PLUGINS) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupWindowManagement = () => {
|
setupWindowManagement = () => {
|
||||||
let windowCloseable
|
|
||||||
|
|
||||||
app.window.on('show', () => {
|
app.window.on('show', () => {
|
||||||
app.window.webContents.send('host:window-shown')
|
app.window.webContents.send('host:window-shown')
|
||||||
})
|
})
|
||||||
|
|
||||||
app.window.on('close', (e) => {
|
app.window.on('close', (e) => {
|
||||||
windowConfig.set('windowBoundaries', app.window.getBounds())
|
windowConfig.set('windowBoundaries', app.window.getBounds())
|
||||||
if (!windowCloseable) {
|
|
||||||
app.window.minimize()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.window.on('closed', () => {
|
app.window.on('closed', () => {
|
||||||
app.window = null
|
app.window = null
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcMain.on('window-closeable', (event, flag) => {
|
|
||||||
windowCloseable = flag
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-focus', () => {
|
electron.ipcMain.on('window-focus', () => {
|
||||||
app.window.focus()
|
app.window.focus()
|
||||||
})
|
})
|
||||||
@@ -85,6 +76,8 @@ setupWindowManagement = () => {
|
|||||||
|
|
||||||
electron.ipcMain.on('window-set-bounds', (event, bounds) => {
|
electron.ipcMain.on('window-set-bounds', (event, bounds) => {
|
||||||
let actualBounds = app.window.getBounds()
|
let actualBounds = app.window.getBounds()
|
||||||
|
actualBounds.width -= bounds.x - actualBounds.x
|
||||||
|
actualBounds.height -= bounds.y - actualBounds.y
|
||||||
actualBounds.x = bounds.x
|
actualBounds.x = bounds.x
|
||||||
actualBounds.y = bounds.y
|
actualBounds.y = bounds.y
|
||||||
app.window.setBounds(actualBounds)
|
app.window.setBounds(actualBounds)
|
||||||
@@ -101,31 +94,83 @@ setupWindowManagement = () => {
|
|||||||
electron.ipcMain.on('window-set-always-on-top', (event, flag) => {
|
electron.ipcMain.on('window-set-always-on-top', (event, flag) => {
|
||||||
app.window.setAlwaysOnTop(flag)
|
app.window.setAlwaysOnTop(flag)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('before-quit', () => windowCloseable = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setupMenu = () => {
|
setupMenu = () => {
|
||||||
var template = [{
|
let template = [{
|
||||||
label: "Application",
|
label: "Application",
|
||||||
submenu: [
|
submenu: [
|
||||||
{ type: "separator" },
|
{ role: 'about', label: 'About Terminus' },
|
||||||
{ label: "Quit", accelerator: "CmdOrCtrl+Q", click: () => {
|
{ type: 'separator' },
|
||||||
app.window.webContents.send('host:quit-request')
|
{
|
||||||
}}
|
label: 'Preferences',
|
||||||
|
accelerator: 'Cmd+,',
|
||||||
|
click () {
|
||||||
|
app.window.webContents.send('host:preferences-menu')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'services', submenu: [] },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'hide' },
|
||||||
|
{ role: 'hideothers' },
|
||||||
|
{ role: 'unhide' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
accelerator: 'Cmd+Q',
|
||||||
|
click () {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Edit",
|
label: "Edit",
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" },
|
{role: 'undo'},
|
||||||
{ label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
|
{role: 'redo'},
|
||||||
{ type: "separator" },
|
{type: 'separator'},
|
||||||
{ label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
|
{role: 'cut'},
|
||||||
{ label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
|
{role: 'copy'},
|
||||||
{ label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
|
{role: 'paste'},
|
||||||
{ label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
|
{role: 'pasteandmatchstyle'},
|
||||||
|
{role: 'delete'},
|
||||||
|
{role: 'selectall'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
submenu: [
|
||||||
|
{role: 'reload'},
|
||||||
|
{role: 'forcereload'},
|
||||||
|
{role: 'toggledevtools'},
|
||||||
|
{type: 'separator'},
|
||||||
|
{role: 'resetzoom'},
|
||||||
|
{role: 'zoomin'},
|
||||||
|
{role: 'zoomout'},
|
||||||
|
{type: 'separator'},
|
||||||
|
{role: 'togglefullscreen'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'window',
|
||||||
|
submenu: [
|
||||||
|
{role: 'close'},
|
||||||
|
{role: 'minimize'},
|
||||||
|
{role: 'zoom'},
|
||||||
|
{type: 'separator'},
|
||||||
|
{role: 'front'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'help',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Website',
|
||||||
|
click () { electron.shell.openExternal('https://eugeny.github.io/terminus') }
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
@@ -12,14 +12,14 @@
|
|||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "4.0.1",
|
"@angular/animations": "4.3.0",
|
||||||
"@angular/common": "4.0.1",
|
"@angular/common": "4.3.0",
|
||||||
"@angular/compiler": "4.0.1",
|
"@angular/compiler": "4.3.0",
|
||||||
"@angular/core": "4.0.1",
|
"@angular/core": "4.3.0",
|
||||||
"@angular/forms": "4.0.1",
|
"@angular/forms": "4.3.0",
|
||||||
"@angular/platform-browser": "4.0.1",
|
"@angular/platform-browser": "4.3.0",
|
||||||
"@angular/platform-browser-dynamic": "4.0.1",
|
"@angular/platform-browser-dynamic": "4.3.0",
|
||||||
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22",
|
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.28",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron-config": "0.2.1",
|
"electron-config": "0.2.1",
|
||||||
"electron-debug": "^1.0.1",
|
"electron-debug": "^1.0.1",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"rxjs": "5.3.0",
|
"rxjs": "5.3.0",
|
||||||
"zone.js": "0.8.4"
|
"zone.js": "0.8.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mz": "0.0.31"
|
"@types/mz": "0.0.31"
|
||||||
|
@@ -31,3 +31,7 @@ process.on('uncaughtException', (err) => {
|
|||||||
Raven.captureException(err)
|
Raven.captureException(err)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const childProcess = require('child_process')
|
||||||
|
childProcess.spawn = require('electron').remote.require('child_process').spawn
|
||||||
|
childProcess.exec = require('electron').remote.require('child_process').exec
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import 'zone.js/dist/zone.js'
|
import 'zone.js'
|
||||||
import 'core-js/es7/reflect'
|
import 'core-js/es7/reflect'
|
||||||
import 'core-js/core/delay'
|
import 'core-js/core/delay'
|
||||||
import 'rxjs'
|
import 'rxjs'
|
||||||
@@ -12,6 +12,10 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
|||||||
import { getRootModule } from './app.module'
|
import { getRootModule } from './app.module'
|
||||||
import { findPlugins, loadPlugins } from './plugins'
|
import { findPlugins, loadPlugins } from './plugins'
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
process.env.HOME = process.env.HOMEDRIVE + process.env.HOMEPATH
|
||||||
|
}
|
||||||
|
|
||||||
if (require('electron-is-dev')) {
|
if (require('electron-is-dev')) {
|
||||||
console.warn('Running in debug mode')
|
console.warn('Running in debug mode')
|
||||||
} else {
|
} else {
|
||||||
|
@@ -20,11 +20,7 @@ if (process.env.DEV) {
|
|||||||
nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
|
nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
|
||||||
}
|
}
|
||||||
|
|
||||||
const builtinPluginsPath = path.join(
|
const builtinPluginsPath = path.join((process as any).resourcesPath, 'builtin-plugins')
|
||||||
path.dirname(require('electron').remote.app.getPath('exe')),
|
|
||||||
(process.platform === 'darwin') ? '../Resources' : 'resources',
|
|
||||||
'builtin-plugins',
|
|
||||||
)
|
|
||||||
|
|
||||||
const userPluginsPath = path.join(
|
const userPluginsPath = path.join(
|
||||||
require('electron').remote.app.getPath('appData'),
|
require('electron').remote.app.getPath('appData'),
|
||||||
@@ -35,9 +31,9 @@ const userPluginsPath = path.join(
|
|||||||
Object.assign(window, { builtinPluginsPath, userPluginsPath })
|
Object.assign(window, { builtinPluginsPath, userPluginsPath })
|
||||||
nodeModule.globalPaths.unshift(builtinPluginsPath)
|
nodeModule.globalPaths.unshift(builtinPluginsPath)
|
||||||
nodeModule.globalPaths.unshift(path.join(userPluginsPath, 'node_modules'))
|
nodeModule.globalPaths.unshift(path.join(userPluginsPath, 'node_modules'))
|
||||||
|
// nodeModule.globalPaths.unshift(path.join((process as any).resourcesPath, 'app.asar', 'node_modules'))
|
||||||
if (process.env.TERMINUS_PLUGINS) {
|
if (process.env.TERMINUS_PLUGINS) {
|
||||||
process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.unshift(normalizePath(x)))
|
process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.push(normalizePath(x)))
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare type ProgressCallback = (current, total) => void
|
export declare type ProgressCallback = (current, total) => void
|
||||||
@@ -48,14 +44,45 @@ export interface IPluginInfo {
|
|||||||
packageName: string
|
packageName: string
|
||||||
isBuiltin: boolean
|
isBuiltin: boolean
|
||||||
version: string
|
version: string
|
||||||
|
author: string
|
||||||
homepage?: string
|
homepage?: string
|
||||||
path?: string
|
path?: string
|
||||||
info?: any
|
info?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const builtinModules = [
|
||||||
|
'@angular/animations',
|
||||||
|
'@angular/common',
|
||||||
|
'@angular/compiler',
|
||||||
|
'@angular/core',
|
||||||
|
'@angular/forms',
|
||||||
|
'@angular/platform-browser',
|
||||||
|
'@angular/platform-browser-dynamic',
|
||||||
|
'@ng-bootstrap/ng-bootstrap',
|
||||||
|
'rxjs',
|
||||||
|
'terminus-core',
|
||||||
|
'terminus-settings',
|
||||||
|
'terminus-terminal',
|
||||||
|
'zone.js/dist/zone.js',
|
||||||
|
]
|
||||||
|
|
||||||
|
const cachedBuiltinModules = {}
|
||||||
|
builtinModules.forEach(m => {
|
||||||
|
cachedBuiltinModules[m] = nodeRequire(m)
|
||||||
|
})
|
||||||
|
|
||||||
|
const originalRequire = nodeRequire('module').prototype.require
|
||||||
|
nodeRequire('module').prototype.require = function (query) {
|
||||||
|
if (cachedBuiltinModules[query]) {
|
||||||
|
return cachedBuiltinModules[query]
|
||||||
|
}
|
||||||
|
return originalRequire.apply(this, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
export async function findPlugins (): Promise<IPluginInfo[]> {
|
export async function findPlugins (): Promise<IPluginInfo[]> {
|
||||||
let paths = nodeModule.globalPaths
|
let paths = nodeModule.globalPaths
|
||||||
let foundPlugins: IPluginInfo[] = []
|
let foundPlugins: IPluginInfo[] = []
|
||||||
|
let candidateLocations: { pluginDir: string, pluginName: string }[] = []
|
||||||
|
|
||||||
for (let pluginDir of paths) {
|
for (let pluginDir of paths) {
|
||||||
pluginDir = normalizePath(pluginDir)
|
pluginDir = normalizePath(pluginDir)
|
||||||
@@ -63,32 +90,47 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let pluginNames = await fs.readdir(pluginDir)
|
let pluginNames = await fs.readdir(pluginDir)
|
||||||
for (let pluginName of pluginNames.filter(x => /^terminus-/.exec(x))) {
|
if (await fs.exists(path.join(pluginDir, 'package.json'))) {
|
||||||
let pluginPath = path.join(pluginDir, pluginName)
|
candidateLocations.push({
|
||||||
let infoPath = path.join(pluginPath, 'package.json')
|
pluginDir: path.dirname(pluginDir),
|
||||||
if (!await fs.exists(infoPath)) {
|
pluginName: path.basename(pluginDir)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for (let pluginName of pluginNames) {
|
||||||
|
candidateLocations.push({ pluginDir, pluginName })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let { pluginDir, pluginName } of candidateLocations) {
|
||||||
|
let pluginPath = path.join(pluginDir, pluginName)
|
||||||
|
let infoPath = path.join(pluginPath, 'package.json')
|
||||||
|
if (!await fs.exists(infoPath)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundPlugins.some(x => x.name === pluginName)) {
|
||||||
|
console.info(`Plugin ${pluginName} already exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let info = JSON.parse(await fs.readFile(infoPath, {encoding: 'utf-8'}))
|
||||||
|
if (!info.keywords || info.keywords.indexOf('terminus-plugin') === -1) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
let author = info.author
|
||||||
if (foundPlugins.some(x => x.name === pluginName)) {
|
author = author.name || author
|
||||||
console.info(`Plugin ${pluginName} already exists`)
|
foundPlugins.push({
|
||||||
}
|
name: pluginName.substring('terminus-'.length),
|
||||||
|
packageName: pluginName,
|
||||||
try {
|
isBuiltin: pluginDir === builtinPluginsPath,
|
||||||
let info = JSON.parse(await fs.readFile(infoPath, {encoding: 'utf-8'}))
|
version: info.version,
|
||||||
console.log(pluginDir, builtinPluginsPath)
|
description: info.description,
|
||||||
foundPlugins.push({
|
author,
|
||||||
name: pluginName.substring('terminus-'.length),
|
path: pluginPath,
|
||||||
packageName: pluginName,
|
info,
|
||||||
isBuiltin: pluginDir === builtinPluginsPath,
|
})
|
||||||
version: info.version,
|
} catch (error) {
|
||||||
description: info.description,
|
console.error('Cannot load package info for', pluginName)
|
||||||
path: pluginPath,
|
|
||||||
info,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Cannot load package info for', pluginName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -55,13 +55,14 @@ module.exports = {
|
|||||||
'@angular/forms': 'commonjs @angular/forms',
|
'@angular/forms': 'commonjs @angular/forms',
|
||||||
'@angular/common': 'commonjs @angular/common',
|
'@angular/common': 'commonjs @angular/common',
|
||||||
'@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
|
'@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
|
||||||
|
'child_process': 'commonjs child_process',
|
||||||
'electron': 'commonjs electron',
|
'electron': 'commonjs electron',
|
||||||
'electron-is-dev': 'commonjs electron-is-dev',
|
'electron-is-dev': 'commonjs electron-is-dev',
|
||||||
'module': 'commonjs module',
|
'module': 'commonjs module',
|
||||||
'mz': 'commonjs mz',
|
'mz': 'commonjs mz',
|
||||||
'path': 'commonjs path',
|
'path': 'commonjs path',
|
||||||
'rxjs': 'commonjs rxjs',
|
'rxjs': 'commonjs rxjs',
|
||||||
'zone.js': 'commonjs zone.js',
|
'zone.js': 'commonjs zone.js/dist/zone.js',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
|
273
app/yarn.lock
Normal file
273
app/yarn.lock
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@angular/animations@4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.3.0.tgz#56f34b84649379202ac359929b82eb0b915e9c72"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.7.1"
|
||||||
|
|
||||||
|
"@angular/common@4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.3.0.tgz#13a54a6929dd52f9729b16ae446fad58fe163053"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.7.1"
|
||||||
|
|
||||||
|
"@angular/compiler@4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.3.0.tgz#55503bf27a1f062f71b9495393f3311903a8fc43"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.7.1"
|
||||||
|
|
||||||
|
"@angular/core@4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.3.0.tgz#bd2249c3de1224a7c6536c4aba728d6565329334"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.7.1"
|
||||||
|
|
||||||
|
"@angular/forms@4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.3.0.tgz#7d0c7a854737e9a30a5fd9665f8d4f56a1b91bd8"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.7.1"
|
||||||
|
|
||||||
|
"@angular/platform-browser-dynamic@4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.3.0.tgz#551fb18851b27ee8f3e4b0ee25aad10bd7b312e3"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.7.1"
|
||||||
|
|
||||||
|
"@angular/platform-browser@4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.3.0.tgz#02389489185185c3becf06359346100e5479c7e1"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.7.1"
|
||||||
|
|
||||||
|
"@ng-bootstrap/ng-bootstrap@^1.0.0-alpha.28":
|
||||||
|
version "1.0.0-alpha.28"
|
||||||
|
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-alpha.28.tgz#30a6503bf7f94f9d3187591fb3267b59cc0cdaad"
|
||||||
|
|
||||||
|
"@types/mz@0.0.31":
|
||||||
|
version "0.0.31"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.31.tgz#a4d80c082fefe71e40a7c0f07d1e6555bbbc7b52"
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/node@*":
|
||||||
|
version "8.0.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.13.tgz#530f0f9254209b0335bf5cc6387822594ef47093"
|
||||||
|
|
||||||
|
accessibility-developer-tools@^2.11.0:
|
||||||
|
version "2.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/accessibility-developer-tools/-/accessibility-developer-tools-2.12.0.tgz#3da0cce9d6ec6373964b84f35db7cfc3df7ab514"
|
||||||
|
|
||||||
|
any-promise@^1.0.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||||
|
|
||||||
|
argparse@^1.0.7:
|
||||||
|
version "1.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
||||||
|
dependencies:
|
||||||
|
sprintf-js "~1.0.2"
|
||||||
|
|
||||||
|
conf@^0.11.1:
|
||||||
|
version "0.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/conf/-/conf-0.11.2.tgz#879f479267600483e502583462ca4063fc9779b2"
|
||||||
|
dependencies:
|
||||||
|
dot-prop "^3.0.0"
|
||||||
|
env-paths "^0.3.0"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
pkg-up "^1.0.0"
|
||||||
|
|
||||||
|
debug@^2.2.0, debug@^2.6.8:
|
||||||
|
version "2.6.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
|
devtron@1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/devtron/-/devtron-1.4.0.tgz#b5e748bd6e95bbe70bfcc68aae6fe696119441e1"
|
||||||
|
dependencies:
|
||||||
|
accessibility-developer-tools "^2.11.0"
|
||||||
|
highlight.js "^9.3.0"
|
||||||
|
humanize-plus "^1.8.1"
|
||||||
|
|
||||||
|
dot-prop@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177"
|
||||||
|
dependencies:
|
||||||
|
is-obj "^1.0.0"
|
||||||
|
|
||||||
|
electron-config@0.2.1:
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-config/-/electron-config-0.2.1.tgz#7e12c26412d06bf3ed3896d0479df162986b95ba"
|
||||||
|
dependencies:
|
||||||
|
conf "^0.11.1"
|
||||||
|
|
||||||
|
electron-debug@^1.0.1:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-debug/-/electron-debug-1.2.0.tgz#22e51a73e1bf095d0bb51a6c3d97a203364c4222"
|
||||||
|
dependencies:
|
||||||
|
electron-is-dev "^0.1.0"
|
||||||
|
electron-localshortcut "^2.0.0"
|
||||||
|
|
||||||
|
electron-is-accelerator@^0.1.0:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b"
|
||||||
|
|
||||||
|
electron-is-dev@0.1.2, electron-is-dev@^0.1.0:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.1.2.tgz#8a1043e32b3a1da1c3f553dce28ce764246167e3"
|
||||||
|
|
||||||
|
electron-localshortcut@^2.0.0:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-localshortcut/-/electron-localshortcut-2.0.2.tgz#6a1adcd6514c957328ec7912f5ccb5e1c10706db"
|
||||||
|
dependencies:
|
||||||
|
debug "^2.6.8"
|
||||||
|
electron-is-accelerator "^0.1.0"
|
||||||
|
|
||||||
|
electron-squirrel-startup@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/electron-squirrel-startup/-/electron-squirrel-startup-1.0.0.tgz#19b4e55933fa0ef8f556784b9c660f772546a0b8"
|
||||||
|
dependencies:
|
||||||
|
debug "^2.2.0"
|
||||||
|
|
||||||
|
env-paths@^0.3.0:
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-0.3.1.tgz#c30ccfcbc30c890943dc08a85582517ef00da463"
|
||||||
|
|
||||||
|
esprima@^3.1.1:
|
||||||
|
version "3.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
|
||||||
|
|
||||||
|
find-up@^1.0.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
|
||||||
|
dependencies:
|
||||||
|
path-exists "^2.0.0"
|
||||||
|
pinkie-promise "^2.0.0"
|
||||||
|
|
||||||
|
highlight.js@^9.3.0:
|
||||||
|
version "9.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
|
||||||
|
|
||||||
|
humanize-plus@^1.8.1:
|
||||||
|
version "1.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030"
|
||||||
|
|
||||||
|
inherits@2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
|
||||||
|
|
||||||
|
is-obj@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
|
||||||
|
|
||||||
|
js-yaml@3.8.2:
|
||||||
|
version "3.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.2.tgz#02d3e2c0f6beab20248d412c352203827d786721"
|
||||||
|
dependencies:
|
||||||
|
argparse "^1.0.7"
|
||||||
|
esprima "^3.1.1"
|
||||||
|
|
||||||
|
minimist@0.0.8:
|
||||||
|
version "0.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
|
|
||||||
|
mkdirp@^0.5.1:
|
||||||
|
version "0.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
|
dependencies:
|
||||||
|
minimist "0.0.8"
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
|
||||||
|
mz@^2.6.0:
|
||||||
|
version "2.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mz/-/mz-2.6.0.tgz#c8b8521d958df0a4f2768025db69c719ee4ef1ce"
|
||||||
|
dependencies:
|
||||||
|
any-promise "^1.0.0"
|
||||||
|
object-assign "^4.0.1"
|
||||||
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
|
object-assign@^4.0.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
|
||||||
|
path-exists@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
|
||||||
|
dependencies:
|
||||||
|
pinkie-promise "^2.0.0"
|
||||||
|
|
||||||
|
path@0.12.7:
|
||||||
|
version "0.12.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
|
||||||
|
dependencies:
|
||||||
|
process "^0.11.1"
|
||||||
|
util "^0.10.3"
|
||||||
|
|
||||||
|
pinkie-promise@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||||
|
dependencies:
|
||||||
|
pinkie "^2.0.0"
|
||||||
|
|
||||||
|
pinkie@^2.0.0:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||||
|
|
||||||
|
pkg-up@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26"
|
||||||
|
dependencies:
|
||||||
|
find-up "^1.0.0"
|
||||||
|
|
||||||
|
process@^0.11.1:
|
||||||
|
version "0.11.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||||
|
|
||||||
|
rxjs@5.3.0:
|
||||||
|
version "5.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.3.0.tgz#d88ccbdd46af290cbdb97d5d8055e52453fabe2d"
|
||||||
|
dependencies:
|
||||||
|
symbol-observable "^1.0.1"
|
||||||
|
|
||||||
|
sprintf-js@~1.0.2:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
|
|
||||||
|
symbol-observable@^1.0.1:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
|
||||||
|
|
||||||
|
thenify-all@^1.0.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
||||||
|
dependencies:
|
||||||
|
thenify ">= 3.1.0 < 4"
|
||||||
|
|
||||||
|
"thenify@>= 3.1.0 < 4":
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
|
||||||
|
dependencies:
|
||||||
|
any-promise "^1.0.0"
|
||||||
|
|
||||||
|
tslib@^1.7.1:
|
||||||
|
version "1.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec"
|
||||||
|
|
||||||
|
util@^0.10.3:
|
||||||
|
version "0.10.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
|
||||||
|
dependencies:
|
||||||
|
inherits "2.0.1"
|
||||||
|
|
||||||
|
zone.js@0.8.12:
|
||||||
|
version "0.8.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.12.tgz#86ff5053c98aec291a0bf4bbac501d694a05cfbb"
|
BIN
docs/linux.png
BIN
docs/linux.png
Binary file not shown.
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 138 KiB |
23
package.json
23
package.json
@@ -9,7 +9,7 @@
|
|||||||
"core-js": "2.4.1",
|
"core-js": "2.4.1",
|
||||||
"cross-env": "4.0.0",
|
"cross-env": "4.0.0",
|
||||||
"css-loader": "0.28.0",
|
"css-loader": "0.28.0",
|
||||||
"electron": "1.7.2",
|
"electron": "1.6.11",
|
||||||
"electron-builder": "17.1.1",
|
"electron-builder": "17.1.1",
|
||||||
"electron-builder-squirrel-windows": "17.0.1",
|
"electron-builder-squirrel-windows": "17.0.1",
|
||||||
"electron-rebuild": "1.5.11",
|
"electron-rebuild": "1.5.11",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"less": "2.7.1",
|
"less": "2.7.1",
|
||||||
"less-loader": "2.2.3",
|
"less-loader": "2.2.3",
|
||||||
"node-abi": "2.0.3",
|
"node-abi": "2.0.3",
|
||||||
"node-gyp": "3.4.0",
|
"node-gyp": "^3.6.2",
|
||||||
"node-sass": "^4.5.3",
|
"node-sass": "^4.5.3",
|
||||||
"npmlog": "4.1.0",
|
"npmlog": "4.1.0",
|
||||||
"pug": "2.0.0-beta11",
|
"pug": "2.0.0-beta11",
|
||||||
@@ -65,14 +65,31 @@
|
|||||||
"linux": {
|
"linux": {
|
||||||
"category": "Utilities",
|
"category": "Utilities",
|
||||||
"icon": "./build/icons"
|
"icon": "./build/icons"
|
||||||
|
},
|
||||||
|
"deb": {
|
||||||
|
"depends": [
|
||||||
|
"screen",
|
||||||
|
"gconf2",
|
||||||
|
"gconf-service",
|
||||||
|
"libnotify4",
|
||||||
|
"libappindicator1",
|
||||||
|
"libxtst6",
|
||||||
|
"libnss3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rpm": {
|
||||||
|
"depends": [
|
||||||
|
"screen"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js",
|
"build": "webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js",
|
||||||
"watch": "webpack --progress --color --watch",
|
"watch": "webpack --progress --color --watch",
|
||||||
"start": "cross-env DEV=1 electron --js-flags='--ignition' app --debug",
|
"start": "cross-env DEV=1 electron --js-flags='--ignition' app --debug",
|
||||||
|
"prod": "cross-env DEV=1 electron --js-flags='--ignition' app",
|
||||||
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
||||||
"postinstall": "install-app-deps"
|
"postinstall": "install-app-deps"
|
||||||
},
|
},
|
||||||
"false": {}
|
"repository": "eugeny/terminus"
|
||||||
}
|
}
|
||||||
|
@@ -5,21 +5,18 @@ const vars = require('./vars')
|
|||||||
const log = require('npmlog')
|
const log = require('npmlog')
|
||||||
|
|
||||||
log.info('deps', 'app')
|
log.info('deps', 'app')
|
||||||
sh.exec('npm prune')
|
sh.exec('yarn prune')
|
||||||
sh.exec('npm install')
|
sh.exec('yarn install')
|
||||||
sh.exec('npm update --dev')
|
|
||||||
|
|
||||||
sh.cd('app')
|
sh.cd('app')
|
||||||
sh.exec('npm prune')
|
sh.exec('yarn prune')
|
||||||
sh.exec('npm install')
|
sh.exec('yarn install')
|
||||||
sh.exec('npm update --dev')
|
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
|
|
||||||
vars.builtinPlugins.forEach(plugin => {
|
vars.builtinPlugins.forEach(plugin => {
|
||||||
log.info('deps', plugin)
|
log.info('deps', plugin)
|
||||||
sh.cd(plugin)
|
sh.cd(plugin)
|
||||||
sh.exec('npm prune')
|
sh.exec('yarn prune')
|
||||||
sh.exec('npm install')
|
sh.exec('yarn install')
|
||||||
sh.exec('npm update --dev')
|
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
})
|
})
|
||||||
|
@@ -22,3 +22,4 @@ vars.builtinPlugins.forEach(plugin => {
|
|||||||
}
|
}
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
})
|
})
|
||||||
|
fs.unlinkSync(path.join(target, 'package.json'), '{}')
|
||||||
|
11
scripts/set-plugin-versions.js
Executable file
11
scripts/set-plugin-versions.js
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const sh = require('shelljs')
|
||||||
|
const vars = require('./vars')
|
||||||
|
const log = require('npmlog')
|
||||||
|
|
||||||
|
vars.builtinPlugins.forEach(plugin => {
|
||||||
|
log.info('bump', plugin)
|
||||||
|
sh.cd(plugin)
|
||||||
|
sh.exec('npm --no-git-tag-version version ' + vars.version)
|
||||||
|
sh.cd('..')
|
||||||
|
})
|
@@ -16,5 +16,4 @@ exports.builtinPlugins = [
|
|||||||
'terminus-plugin-manager',
|
'terminus-plugin-manager',
|
||||||
]
|
]
|
||||||
exports.nativeModules = ['node-pty', 'font-manager']
|
exports.nativeModules = ['node-pty', 'font-manager']
|
||||||
exports.version = appInfo.version
|
|
||||||
exports.electronVersion = pkgInfo.devDependencies.electron
|
exports.electronVersion = pkgInfo.devDependencies.electron
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-community-color-schemes",
|
"name": "terminus-community-color-schemes",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0-alpha.16-8-gfc060ac",
|
||||||
"description": "Community color schemes for Terminus",
|
"description": "Community color schemes for Terminus",
|
||||||
|
"keywords": [
|
||||||
|
"terminus-plugin"
|
||||||
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
44
terminus-community-color-schemes/schemes/Base16 Default Dark
Normal file
44
terminus-community-color-schemes/schemes/Base16 Default Dark
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xreources.py
|
||||||
|
!
|
||||||
|
*.foreground: #d8d8d8
|
||||||
|
*.background: #181818
|
||||||
|
*.cursorColor: #d8d8d8
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #181818
|
||||||
|
*.color8: #585858
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #ab4642
|
||||||
|
*.color9: #ab4642
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #a1b56c
|
||||||
|
*.color10: #a1b56c
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #f7ca88
|
||||||
|
*.color11: #f7ca88
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #7cafc2
|
||||||
|
*.color12: #7cafc2
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #ba8baf
|
||||||
|
*.color13: #ba8baf
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #86c1b9
|
||||||
|
*.color14: #86c1b9
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #d8d8d8
|
||||||
|
*.color15: #f8f8f8
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #d8d8d8
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
44
terminus-community-color-schemes/schemes/base2tone-cave-dark
Normal file
44
terminus-community-color-schemes/schemes/base2tone-cave-dark
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #9f999b
|
||||||
|
*.background: #222021
|
||||||
|
*.cursorColor: #996e00
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #222021
|
||||||
|
*.color8: #635f60
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #936c7a
|
||||||
|
*.color9: #ddaf3c
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #cca133
|
||||||
|
*.color10: #2f2d2e
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #ffcc4d
|
||||||
|
*.color11: #565254
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #9c818b
|
||||||
|
*.color12: #706b6d
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #cca133
|
||||||
|
*.color13: #f0a8c1
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #d27998
|
||||||
|
*.color14: #c39622
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #9f999b
|
||||||
|
*.color15: #ffebf2
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #9f999b
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #ada594
|
||||||
|
*.background: #292724
|
||||||
|
*.cursorColor: #bc672f
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #292724
|
||||||
|
*.color8: #7e7767
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #816f4b
|
||||||
|
*.color9: #f29d63
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #ec9255
|
||||||
|
*.color10: #3d3a34
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #ffb380
|
||||||
|
*.color11: #615c51
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #957e50
|
||||||
|
*.color12: #908774
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #ec9255
|
||||||
|
*.color13: #ddcba6
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #ac8e53
|
||||||
|
*.color14: #e58748
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #ada594
|
||||||
|
*.color15: #f2ead9
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #ada594
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #9094a7
|
||||||
|
*.background: #1b1f32
|
||||||
|
*.cursorColor: #289dbd
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #1b1f32
|
||||||
|
*.color8: #51587b
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #627af4
|
||||||
|
*.color9: #75d5f0
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #67c9e4
|
||||||
|
*.color10: #252a41
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #99e9ff
|
||||||
|
*.color11: #444b6f
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #7289fd
|
||||||
|
*.color12: #5e6587
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #67c9e4
|
||||||
|
*.color13: #c3cdfe
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #8b9efd
|
||||||
|
*.color14: #5cbcd6
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #9094a7
|
||||||
|
*.color15: #e1e6ff
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #9094a7
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #a4a1b5
|
||||||
|
*.background: #2a2734
|
||||||
|
*.cursorColor: #b37537
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #2a2734
|
||||||
|
*.color8: #6c6783
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #8a75f5
|
||||||
|
*.color9: #ffb870
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #ffad5c
|
||||||
|
*.color10: #363342
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #ffcc99
|
||||||
|
*.color11: #545167
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #9a86fd
|
||||||
|
*.color12: #787391
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #ffad5c
|
||||||
|
*.color13: #d9d2fe
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #afa0fe
|
||||||
|
*.color14: #ffa142
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #a4a1b5
|
||||||
|
*.color15: #eeebff
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #a4a1b5
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #a1b5a1
|
||||||
|
*.background: #2a2d2a
|
||||||
|
*.cursorColor: #656b47
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #2a2d2a
|
||||||
|
*.color8: #535f53
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #5c705c
|
||||||
|
*.color9: #cbe25a
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #bfd454
|
||||||
|
*.color10: #353b35
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #e5fb79
|
||||||
|
*.color11: #485148
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #687d68
|
||||||
|
*.color12: #5e6e5e
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #bfd454
|
||||||
|
*.color13: #c8e4c8
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #8fae8f
|
||||||
|
*.color14: #b1c44f
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #a1b5a1
|
||||||
|
*.color15: #f0fff0
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #a1b5a1
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #9e999f
|
||||||
|
*.background: #222022
|
||||||
|
*.cursorColor: #995900
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #222022
|
||||||
|
*.color8: #635f63
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #8f6c93
|
||||||
|
*.color9: #d9b98c
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #cc8c33
|
||||||
|
*.color10: #2f2d2f
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #ffd599
|
||||||
|
*.color11: #575158
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #9a819c
|
||||||
|
*.color12: #6f6b70
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #cc8c33
|
||||||
|
*.color13: #eaa8f0
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #cb79d2
|
||||||
|
*.color14: #c38022
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #9e999f
|
||||||
|
*.color15: #fdebff
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #9e999f
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #575158
|
||||||
|
*.background: #fbfaf9
|
||||||
|
*.cursorColor: #eaa8f0
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #222022
|
||||||
|
*.color8: #635f63
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #8f6c93
|
||||||
|
*.color9: #d9b98c
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #cc8c33
|
||||||
|
*.color10: #2f2d2f
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #ffd599
|
||||||
|
*.color11: #575158
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #9a819c
|
||||||
|
*.color12: #6f6b70
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #b87414
|
||||||
|
*.color13: #eaa8f0
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #cb79d2
|
||||||
|
*.color14: #c38022
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #9e999f
|
||||||
|
*.color15: #fbfaf9
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #575158
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
44
terminus-community-color-schemes/schemes/base2tone-lake-dark
Normal file
44
terminus-community-color-schemes/schemes/base2tone-lake-dark
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #7ba8b7
|
||||||
|
*.background: #192d34
|
||||||
|
*.cursorColor: #84740b
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #192d34
|
||||||
|
*.color8: #3d6876
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #3e91ac
|
||||||
|
*.color9: #d6c65c
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #cbbb4d
|
||||||
|
*.color10: #223c44
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #ffeb66
|
||||||
|
*.color11: #335966
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #499fbc
|
||||||
|
*.color12: #467686
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #cbbb4d
|
||||||
|
*.color13: #a5d8e9
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #62b1cb
|
||||||
|
*.color14: #c4b031
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #7ba8b7
|
||||||
|
*.color15: #e1f7ff
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #7ba8b7
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #7b9eb7
|
||||||
|
*.background: #192834
|
||||||
|
*.cursorColor: #4d8217
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #192834
|
||||||
|
*.color8: #3d5e76
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #277fbe
|
||||||
|
*.color9: #8cdd3c
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #80bf40
|
||||||
|
*.color10: #223644
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #a6f655
|
||||||
|
*.color11: #335166
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #4299d7
|
||||||
|
*.color12: #466b86
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #80bf40
|
||||||
|
*.color13: #afddfe
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #47adf5
|
||||||
|
*.color14: #73b234
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #7b9eb7
|
||||||
|
*.color15: #d1ecff
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #7b9eb7
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #4f5664
|
||||||
|
*.background: #faf8f5
|
||||||
|
*.cursorColor: #b7c9eb
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #232834
|
||||||
|
*.color8: #656e81
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #1659df
|
||||||
|
*.color9: #c6b28b
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #b29762
|
||||||
|
*.color10: #31363f
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #e5ddcd
|
||||||
|
*.color11: #4f5664
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #3d75e6
|
||||||
|
*.color12: #707a8f
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #896724
|
||||||
|
*.color13: #b7c9eb
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #728fcb
|
||||||
|
*.color14: #9a7c42
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #8d95a5
|
||||||
|
*.color15: #faf8f5
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #4f5664
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
44
terminus-community-color-schemes/schemes/base2tone-pool-dark
Normal file
44
terminus-community-color-schemes/schemes/base2tone-pool-dark
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #9a90a7
|
||||||
|
*.background: #2a2433
|
||||||
|
*.cursorColor: #cf504a
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #2a2433
|
||||||
|
*.color8: #635775
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #aa75f5
|
||||||
|
*.color9: #fc8983
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #f87972
|
||||||
|
*.color10: #372f42
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #ffb6b3
|
||||||
|
*.color11: #574b68
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #b886fd
|
||||||
|
*.color12: #706383
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #f87972
|
||||||
|
*.color13: #e4d2fe
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #c7a0fe
|
||||||
|
*.color14: #f36f68
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #9a90a7
|
||||||
|
*.color15: #f3ebff
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #9a90a7
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
44
terminus-community-color-schemes/schemes/base2tone-sea-dark
Normal file
44
terminus-community-color-schemes/schemes/base2tone-sea-dark
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #a1aab5
|
||||||
|
*.background: #1d262f
|
||||||
|
*.cursorColor: #067953
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #1d262f
|
||||||
|
*.color8: #4a5f78
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #34659d
|
||||||
|
*.color9: #14e19d
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #0fc78a
|
||||||
|
*.color10: #27323f
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #47ebb4
|
||||||
|
*.color11: #405368
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #57718e
|
||||||
|
*.color12: #738191
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #0fc78a
|
||||||
|
*.color13: #afd4fe
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #6e9bcf
|
||||||
|
*.color14: #0db57d
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #a1aab5
|
||||||
|
*.color15: #ebf4ff
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #a1aab5
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -0,0 +1,44 @@
|
|||||||
|
!
|
||||||
|
! Generated with :
|
||||||
|
! XRDB2Xrecources.py
|
||||||
|
!
|
||||||
|
*.foreground: #a1a1b5
|
||||||
|
*.background: #24242e
|
||||||
|
*.cursorColor: #b25424
|
||||||
|
!
|
||||||
|
! Black
|
||||||
|
*.color0: #24242e
|
||||||
|
*.color8: #5b5b76
|
||||||
|
!
|
||||||
|
! Red
|
||||||
|
*.color1: #7676f4
|
||||||
|
*.color9: #f37b3f
|
||||||
|
!
|
||||||
|
! Green
|
||||||
|
*.color2: #ec7336
|
||||||
|
*.color10: #333342
|
||||||
|
!
|
||||||
|
! Yellow
|
||||||
|
*.color3: #fe8c52
|
||||||
|
*.color11: #515167
|
||||||
|
!
|
||||||
|
! Blue
|
||||||
|
*.color4: #767693
|
||||||
|
*.color12: #737391
|
||||||
|
!
|
||||||
|
! Magenta
|
||||||
|
*.color5: #ec7336
|
||||||
|
*.color13: #cecee3
|
||||||
|
!
|
||||||
|
! Cyan
|
||||||
|
*.color6: #8a8aad
|
||||||
|
*.color14: #e66e33
|
||||||
|
!
|
||||||
|
! White
|
||||||
|
*.color7: #a1a1b5
|
||||||
|
*.color15: #ebebff
|
||||||
|
!
|
||||||
|
! Bold, Italic, Underline
|
||||||
|
*.colorBD: #a1a1b5
|
||||||
|
!*.colorIT:
|
||||||
|
!*.colorUL:
|
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-core",
|
"name": "terminus-core",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0-alpha.16-8-gfc060ac",
|
||||||
"description": "Terminus core",
|
"description": "Terminus core",
|
||||||
|
"keywords": [
|
||||||
|
"terminus-plugin"
|
||||||
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -14,14 +17,13 @@
|
|||||||
"author": "Eugene Pankov",
|
"author": "Eugene Pankov",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22",
|
"@types/js-yaml": "^3.9.0",
|
||||||
"@types/js-yaml": "^3.5.29",
|
"@types/node": "^7.0.37",
|
||||||
"@types/node": "^7.0.12",
|
|
||||||
"@types/webpack-env": "^1.13.0",
|
"@types/webpack-env": "^1.13.0",
|
||||||
"bootstrap": "4.0.0-alpha.6",
|
"bootstrap": "4.0.0-alpha.6",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"ngx-perfect-scrollbar": "4.0.0",
|
"ngx-perfect-scrollbar": "4.0.0",
|
||||||
"typescript": "^2.4.0"
|
"typescript": "^2.4.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/animations": "4.0.1",
|
"@angular/animations": "4.0.1",
|
||||||
@@ -34,8 +36,8 @@
|
|||||||
"zone.js": "0.8.4"
|
"zone.js": "0.8.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"deepmerge": "^1.4.4",
|
"deepmerge": "^1.5.0",
|
||||||
"js-yaml": "^3.8.4"
|
"js-yaml": "^3.9.0"
|
||||||
},
|
},
|
||||||
"false": {}
|
"false": {}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
export { BaseTabComponent } from '../components/baseTab.component'
|
export { BaseTabComponent } from '../components/baseTab.component'
|
||||||
export { TabRecoveryProvider } from './tabRecovery'
|
export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
|
||||||
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
|
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
|
||||||
export { ConfigProvider } from './configProvider'
|
export { ConfigProvider } from './configProvider'
|
||||||
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
|
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
|
||||||
|
@@ -1,3 +1,10 @@
|
|||||||
export abstract class TabRecoveryProvider {
|
import { TabComponentType } from '../services/app.service'
|
||||||
abstract async recover (recoveryToken: any): Promise<void>
|
|
||||||
|
export interface RecoveredTab {
|
||||||
|
type: TabComponentType,
|
||||||
|
options?: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class TabRecoveryProvider {
|
||||||
|
abstract async recover (recoveryToken: any): Promise<RecoveredTab|null>
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ title-bar(
|
|||||||
[class.drag-region]='hostApp.platform == Platform.macOS',
|
[class.drag-region]='hostApp.platform == Platform.macOS',
|
||||||
@animateTab,
|
@animateTab,
|
||||||
(click)='app.selectTab(tab)',
|
(click)='app.selectTab(tab)',
|
||||||
(closeClicked)='app.closeTab(tab)',
|
(closeClicked)='app.closeTab(tab, true)',
|
||||||
)
|
)
|
||||||
|
|
||||||
.btn-group
|
.btn-group
|
||||||
|
@@ -5,7 +5,6 @@ import { ElectronService } from '../services/electron.service'
|
|||||||
import { HostAppService, Platform } from '../services/hostApp.service'
|
import { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
import { HotkeysService } from '../services/hotkeys.service'
|
import { HotkeysService } from '../services/hotkeys.service'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from '../services/log.service'
|
||||||
import { QuitterService } from '../services/quitter.service'
|
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
import { DockingService } from '../services/docking.service'
|
import { DockingService } from '../services/docking.service'
|
||||||
import { TabRecoveryService } from '../services/tabRecovery.service'
|
import { TabRecoveryService } from '../services/tabRecovery.service'
|
||||||
@@ -28,9 +27,16 @@ import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
|||||||
'flex-basis': '1px',
|
'flex-basis': '1px',
|
||||||
'width': '1px',
|
'width': '1px',
|
||||||
}),
|
}),
|
||||||
animate('250ms ease-in-out')
|
animate('250ms ease-in-out', style({
|
||||||
|
'flex-basis': '200px',
|
||||||
|
'width': '200px',
|
||||||
|
}))
|
||||||
]),
|
]),
|
||||||
transition(':leave', [
|
transition(':leave', [
|
||||||
|
style({
|
||||||
|
'flex-basis': '200px',
|
||||||
|
'width': '200px',
|
||||||
|
}),
|
||||||
animate('250ms ease-in-out', style({
|
animate('250ms ease-in-out', style({
|
||||||
'flex-basis': '1px',
|
'flex-basis': '1px',
|
||||||
'width': '1px',
|
'width': '1px',
|
||||||
@@ -50,14 +56,13 @@ export class AppRootComponent {
|
|||||||
private docking: DockingService,
|
private docking: DockingService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private tabRecovery: TabRecoveryService,
|
private tabRecovery: TabRecoveryService,
|
||||||
|
private hotkeys: HotkeysService,
|
||||||
public hostApp: HostAppService,
|
public hostApp: HostAppService,
|
||||||
public hotkeys: HotkeysService,
|
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
log: LogService,
|
log: LogService,
|
||||||
_themes: ThemesService,
|
_themes: ThemesService,
|
||||||
_quitter: QuitterService,
|
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('main')
|
this.logger = log.create('main')
|
||||||
this.logger.info('v', electron.app.getVersion())
|
this.logger.info('v', electron.app.getVersion())
|
||||||
@@ -74,7 +79,7 @@ export class AppRootComponent {
|
|||||||
}
|
}
|
||||||
if (this.app.activeTab) {
|
if (this.app.activeTab) {
|
||||||
if (hotkey === 'close-tab') {
|
if (hotkey === 'close-tab') {
|
||||||
this.app.closeTab(this.app.activeTab)
|
this.app.closeTab(this.app.activeTab, true)
|
||||||
}
|
}
|
||||||
if (hotkey === 'toggle-last-tab') {
|
if (hotkey === 'toggle-last-tab') {
|
||||||
this.app.toggleLastTab()
|
this.app.toggleLastTab()
|
||||||
@@ -93,7 +98,7 @@ export class AppRootComponent {
|
|||||||
this.docking.dock()
|
this.docking.dock()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.hostApp.secondInstance.subscribe(() => {
|
this.hostApp.secondInstance$.subscribe(() => {
|
||||||
this.onGlobalHotkey()
|
this.onGlobalHotkey()
|
||||||
})
|
})
|
||||||
this.hotkeys.globalHotkey.subscribe(() => {
|
this.hotkeys.globalHotkey.subscribe(() => {
|
||||||
@@ -133,16 +138,6 @@ export class AppRootComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
|
|
||||||
let buttons: IToolbarButton[] = []
|
|
||||||
this.toolbarButtonProviders.forEach((provider) => {
|
|
||||||
buttons = buttons.concat(provider.provide())
|
|
||||||
})
|
|
||||||
return buttons
|
|
||||||
.filter((button) => (button.weight > 0) === aboveZero)
|
|
||||||
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragover')
|
@HostListener('dragover')
|
||||||
onDragOver () {
|
onDragOver () {
|
||||||
return false
|
return false
|
||||||
@@ -152,4 +147,14 @@ export class AppRootComponent {
|
|||||||
onDrop () {
|
onDrop () {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
|
||||||
|
let buttons: IToolbarButton[] = []
|
||||||
|
this.toolbarButtonProviders.forEach((provider) => {
|
||||||
|
buttons = buttons.concat(provider.provide())
|
||||||
|
})
|
||||||
|
return buttons
|
||||||
|
.filter((button) => (button.weight > 0) === aboveZero)
|
||||||
|
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { Subject, BehaviorSubject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { ViewRef } from '@angular/core'
|
import { ViewRef } from '@angular/core'
|
||||||
|
|
||||||
export abstract class BaseTabComponent {
|
export abstract class BaseTabComponent {
|
||||||
private static lastTabID = 0
|
private static lastTabID = 0
|
||||||
id: number
|
id: number
|
||||||
title$ = new BehaviorSubject<string>(null)
|
title: string
|
||||||
|
customTitle: string
|
||||||
scrollable: boolean
|
scrollable: boolean
|
||||||
hasActivity = false
|
hasActivity = false
|
||||||
focused$ = new Subject<void>()
|
focused$ = new Subject<void>()
|
||||||
@@ -30,9 +31,12 @@ export abstract class BaseTabComponent {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async canClose (): Promise<boolean> {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
destroy (): void {
|
destroy (): void {
|
||||||
this.focused$.complete()
|
this.focused$.complete()
|
||||||
this.blurred$.complete()
|
this.blurred$.complete()
|
||||||
this.title$.complete()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
.modal-body
|
||||||
|
input.form-control(type='text', [(ngModel)]='value', (keyup.enter)='save()', autofocus)
|
||||||
|
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-outline-primary((click)='save()') Save
|
||||||
|
button.btn.btn-outline-secondary((click)='close()') Cancel
|
22
terminus-core/src/components/renameTabModal.component.ts
Normal file
22
terminus-core/src/components/renameTabModal.component.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, Input } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'rename-tab-modal',
|
||||||
|
template: require('./renameTabModal.component.pug'),
|
||||||
|
})
|
||||||
|
export class RenameTabModalComponent {
|
||||||
|
@Input() value: string
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private modalInstance: NgbActiveModal
|
||||||
|
) { }
|
||||||
|
|
||||||
|
save () {
|
||||||
|
this.modalInstance.close(this.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
close () {
|
||||||
|
this.modalInstance.dismiss()
|
||||||
|
}
|
||||||
|
}
|
@@ -25,6 +25,9 @@ div
|
|||||||
span {{button.title}}
|
span {{button.title}}
|
||||||
|
|
||||||
footer
|
footer
|
||||||
|
.pull-right
|
||||||
|
.form-control-static Version: {{version}}
|
||||||
|
|
||||||
.btn-group
|
.btn-group
|
||||||
button.btn.btn-secondary((click)='openGitHub()')
|
button.btn.btn-secondary((click)='openGitHub()')
|
||||||
i.fa.fa-github
|
i.fa.fa-github
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import * as os from 'os'
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
@@ -8,10 +9,14 @@ import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
|||||||
styles: [require('./startPage.component.scss')],
|
styles: [require('./startPage.component.scss')],
|
||||||
})
|
})
|
||||||
export class StartPageComponent {
|
export class StartPageComponent {
|
||||||
|
version: string
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
) { }
|
) {
|
||||||
|
this.version = electron.app.getVersion()
|
||||||
|
}
|
||||||
|
|
||||||
getButtons (): IToolbarButton[] {
|
getButtons (): IToolbarButton[] {
|
||||||
return this.toolbarButtonProviders
|
return this.toolbarButtonProviders
|
||||||
@@ -25,6 +30,13 @@ export class StartPageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reportBug () {
|
reportBug () {
|
||||||
this.electron.shell.openExternal('https://github.com/eugeny/terminus/issues/new')
|
let body = `Version: ${this.version}\n`
|
||||||
|
body += `Platform: ${os.platform()} ${os.release()}\n\n`
|
||||||
|
let label = {
|
||||||
|
darwin: 'macOS',
|
||||||
|
windows: 'Windows',
|
||||||
|
linux: 'Linux',
|
||||||
|
}[os.platform()]
|
||||||
|
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
.index {{index + 1}}
|
.index {{index + 1}}
|
||||||
.name {{tab.title$ | async}}
|
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
||||||
button((click)='closeClicked.emit()') ×
|
button((click)='closeClicked.emit()') ×
|
||||||
|
@@ -54,6 +54,10 @@ $tabs-height: 36px;
|
|||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover button {
|
&:hover button {
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/core'
|
import { Component, Input, Output, EventEmitter, HostBinding, HostListener } from '@angular/core'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { BaseTabComponent } from './baseTab.component'
|
||||||
|
import { RenameTabModalComponent } from './renameTabModal.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tab-header',
|
selector: 'tab-header',
|
||||||
@@ -12,4 +14,22 @@ export class TabHeaderComponent {
|
|||||||
@Input() @HostBinding('class.has-activity') hasActivity: boolean
|
@Input() @HostBinding('class.has-activity') hasActivity: boolean
|
||||||
@Input() tab: BaseTabComponent
|
@Input() tab: BaseTabComponent
|
||||||
@Output() closeClicked = new EventEmitter()
|
@Output() closeClicked = new EventEmitter()
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private ngbModal: NgbModal,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
@HostListener('dblclick') onDoubleClick (): void {
|
||||||
|
let modal = this.ngbModal.open(RenameTabModalComponent)
|
||||||
|
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
||||||
|
modal.result.then(result => {
|
||||||
|
this.tab.customTitle = result
|
||||||
|
}).catch(() => null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('auxclick', ['$event']) onAuxClick ($event: MouseEvent): void {
|
||||||
|
if ($event.which === 2) {
|
||||||
|
this.closeClicked.emit()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,6 @@ import { ElectronService } from './services/electron.service'
|
|||||||
import { HostAppService } from './services/hostApp.service'
|
import { HostAppService } from './services/hostApp.service'
|
||||||
import { LogService } from './services/log.service'
|
import { LogService } from './services/log.service'
|
||||||
import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service'
|
import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service'
|
||||||
import { QuitterService } from './services/quitter.service'
|
|
||||||
import { DockingService } from './services/docking.service'
|
import { DockingService } from './services/docking.service'
|
||||||
import { TabRecoveryService } from './services/tabRecovery.service'
|
import { TabRecoveryService } from './services/tabRecovery.service'
|
||||||
import { ThemesService } from './services/themes.service'
|
import { ThemesService } from './services/themes.service'
|
||||||
@@ -22,6 +21,7 @@ import { StartPageComponent } from './components/startPage.component'
|
|||||||
import { TabHeaderComponent } from './components/tabHeader.component'
|
import { TabHeaderComponent } from './components/tabHeader.component'
|
||||||
import { TitleBarComponent } from './components/titleBar.component'
|
import { TitleBarComponent } from './components/titleBar.component'
|
||||||
import { WindowControlsComponent } from './components/windowControls.component'
|
import { WindowControlsComponent } from './components/windowControls.component'
|
||||||
|
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
||||||
|
|
||||||
import { HotkeyProvider } from './api/hotkeyProvider'
|
import { HotkeyProvider } from './api/hotkeyProvider'
|
||||||
import { ConfigProvider } from './api/configProvider'
|
import { ConfigProvider } from './api/configProvider'
|
||||||
@@ -42,7 +42,6 @@ const PROVIDERS = [
|
|||||||
LogService,
|
LogService,
|
||||||
TabRecoveryService,
|
TabRecoveryService,
|
||||||
ThemesService,
|
ThemesService,
|
||||||
QuitterService,
|
|
||||||
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
||||||
{ provide: Theme, useClass: StandardTheme, multi: true },
|
{ provide: Theme, useClass: StandardTheme, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
||||||
@@ -65,7 +64,11 @@ const PROVIDERS = [
|
|||||||
TabHeaderComponent,
|
TabHeaderComponent,
|
||||||
TitleBarComponent,
|
TitleBarComponent,
|
||||||
WindowControlsComponent,
|
WindowControlsComponent,
|
||||||
|
RenameTabModalComponent,
|
||||||
],
|
],
|
||||||
|
entryComponents: [
|
||||||
|
RenameTabModalComponent,
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export default class AppModule {
|
export default class AppModule {
|
||||||
static forRoot (): ModuleWithProviders {
|
static forRoot (): ModuleWithProviders {
|
||||||
|
@@ -82,10 +82,16 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closeTab (tab: BaseTabComponent) {
|
async closeTab (tab: BaseTabComponent, checkCanClose?: boolean): Promise<void> {
|
||||||
|
if (!this.tabs.includes(tab)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (checkCanClose && !await tab.canClose()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.tabs = this.tabs.filter((x) => x !== tab)
|
||||||
tab.destroy()
|
tab.destroy()
|
||||||
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
|
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
|
||||||
this.tabs = this.tabs.filter((x) => x !== tab)
|
|
||||||
if (tab === this.activeTab) {
|
if (tab === this.activeTab) {
|
||||||
this.selectTab(this.tabs[newIndex])
|
this.selectTab(this.tabs[newIndex])
|
||||||
}
|
}
|
||||||
|
@@ -67,7 +67,7 @@ export class ConfigService {
|
|||||||
this.defaults = configProviders.map(provider => {
|
this.defaults = configProviders.map(provider => {
|
||||||
let defaults = {}
|
let defaults = {}
|
||||||
if (provider.platformDefaults) {
|
if (provider.platformDefaults) {
|
||||||
defaults = configMerge(defaults, provider.platformDefaults[hostApp.platform])
|
defaults = configMerge(defaults, provider.platformDefaults[hostApp.platform] || {})
|
||||||
}
|
}
|
||||||
if (provider.defaults) {
|
if (provider.defaults) {
|
||||||
defaults = configMerge(defaults, provider.defaults)
|
defaults = configMerge(defaults, provider.defaults)
|
||||||
|
@@ -40,12 +40,12 @@ export class DockingService {
|
|||||||
newBounds.height = Math.round(fill * display.bounds.height)
|
newBounds.height = Math.round(fill * display.bounds.height)
|
||||||
}
|
}
|
||||||
if (dockSide === 'right') {
|
if (dockSide === 'right') {
|
||||||
newBounds.x = display.bounds.x + display.bounds.width * (1.0 - fill)
|
newBounds.x = display.bounds.x + display.bounds.width - newBounds.width
|
||||||
} else {
|
} else {
|
||||||
newBounds.x = display.bounds.x
|
newBounds.x = display.bounds.x
|
||||||
}
|
}
|
||||||
if (dockSide === 'bottom') {
|
if (dockSide === 'bottom') {
|
||||||
newBounds.y = display.bounds.y + display.bounds.height * (1.0 - fill)
|
newBounds.y = display.bounds.y + display.bounds.height - newBounds.height
|
||||||
} else {
|
} else {
|
||||||
newBounds.y = display.bounds.y
|
newBounds.y = display.bounds.y
|
||||||
}
|
}
|
||||||
|
@@ -9,12 +9,12 @@ export class ElectronService {
|
|||||||
clipboard: any
|
clipboard: any
|
||||||
globalShortcut: any
|
globalShortcut: any
|
||||||
screen: any
|
screen: any
|
||||||
|
remote: any
|
||||||
private electron: any
|
private electron: any
|
||||||
private remoteElectron: any
|
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.electron = require('electron')
|
this.electron = require('electron')
|
||||||
this.remoteElectron = this.remoteRequire('electron')
|
this.remote = this.electron.remote
|
||||||
this.app = this.electron.remote.app
|
this.app = this.electron.remote.app
|
||||||
this.screen = this.electron.remote.screen
|
this.screen = this.electron.remote.screen
|
||||||
this.dialog = this.electron.remote.dialog
|
this.dialog = this.electron.remote.dialog
|
||||||
@@ -25,6 +25,10 @@ export class ElectronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remoteRequire (name: string): any {
|
remoteRequire (name: string): any {
|
||||||
return this.electron.remote.require(name)
|
return this.remote.require(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteRequirePluginModule (plugin: string, module: string, globals: any): any {
|
||||||
|
return this.remoteRequire(globals.require.resolve(`${plugin}/node_modules/${module}`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Subject } from 'rxjs'
|
||||||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from '../services/log.service'
|
||||||
@@ -17,10 +18,10 @@ export interface Bounds {
|
|||||||
export class HostAppService {
|
export class HostAppService {
|
||||||
platform: Platform
|
platform: Platform
|
||||||
nodePlatform: string
|
nodePlatform: string
|
||||||
quitRequested = new EventEmitter<any>()
|
preferencesMenu$ = new Subject<void>()
|
||||||
ready = new EventEmitter<any>()
|
ready = new EventEmitter<any>()
|
||||||
shown = new EventEmitter<any>()
|
shown = new EventEmitter<any>()
|
||||||
secondInstance = new EventEmitter<any>()
|
secondInstance$ = new Subject<{ argv: string[], cwd: string }>()
|
||||||
|
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
@@ -37,18 +38,18 @@ export class HostAppService {
|
|||||||
linux: Platform.Linux
|
linux: Platform.Linux
|
||||||
}[this.nodePlatform]
|
}[this.nodePlatform]
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:quit-request', () => this.zone.run(() => this.quitRequested.emit()))
|
electron.ipcRenderer.on('host:preferences-menu', () => this.zone.run(() => this.preferencesMenu$.next()))
|
||||||
|
|
||||||
electron.ipcRenderer.on('uncaughtException', (err) => {
|
electron.ipcRenderer.on('uncaughtException', ($event, err) => {
|
||||||
this.logger.error('Unhandled exception:', err)
|
this.logger.error('Unhandled exception:', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:window-shown', () => {
|
electron.ipcRenderer.on('host:window-shown', () => {
|
||||||
this.shown.emit()
|
this.zone.run(() => this.shown.emit())
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:second-instance', () => {
|
electron.ipcRenderer.on('host:second-instance', ($event, argv: string[], cwd: string) => {
|
||||||
this.secondInstance.emit()
|
this.zone.run(() => this.secondInstance$.next({ argv, cwd }))
|
||||||
})
|
})
|
||||||
|
|
||||||
this.ready.subscribe(() => {
|
this.ready.subscribe(() => {
|
||||||
@@ -76,10 +77,6 @@ export class HostAppService {
|
|||||||
this.getWindow().webContents.openDevTools()
|
this.getWindow().webContents.openDevTools()
|
||||||
}
|
}
|
||||||
|
|
||||||
setCloseable (flag: boolean) {
|
|
||||||
this.electron.ipcRenderer.send('window-set-closeable', flag)
|
|
||||||
}
|
|
||||||
|
|
||||||
focusWindow () {
|
focusWindow () {
|
||||||
this.electron.ipcRenderer.send('window-focus')
|
this.electron.ipcRenderer.send('window-focus')
|
||||||
}
|
}
|
||||||
|
@@ -178,10 +178,6 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'toggle-window',
|
id: 'toggle-window',
|
||||||
name: 'Toggle terminal window',
|
name: 'Toggle terminal window',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'new-tab',
|
|
||||||
name: 'New tab',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'close-tab',
|
id: 'close-tab',
|
||||||
name: 'Close tab',
|
name: 'Close tab',
|
||||||
|
@@ -45,7 +45,9 @@ export function stringifyKeySequence (events: NativeKeyEvent[]): string[] {
|
|||||||
// TODO make this optional?
|
// TODO make this optional?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (event.key.length === 1) {
|
if (event.key === ' ') {
|
||||||
|
itemKeys.push('Space')
|
||||||
|
} else if (event.key.length === 1) {
|
||||||
itemKeys.push(event.key.toUpperCase())
|
itemKeys.push(event.key.toUpperCase())
|
||||||
} else {
|
} else {
|
||||||
itemKeys.push(event.key)
|
itemKeys.push(event.key)
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core'
|
|
||||||
import { HostAppService } from '../services/hostApp.service'
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class QuitterService {
|
|
||||||
constructor (
|
|
||||||
private hostApp: HostAppService,
|
|
||||||
) {
|
|
||||||
hostApp.quitRequested.subscribe(() => {
|
|
||||||
this.quit()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
quit () {
|
|
||||||
this.hostApp.setCloseable(true)
|
|
||||||
this.hostApp.quit()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable, Inject } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { TabRecoveryProvider } from '../api/tabRecovery'
|
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from '../services/log.service'
|
||||||
import { AppService } from '../services/app.service'
|
import { AppService } from '../services/app.service'
|
||||||
@@ -10,7 +10,7 @@ export class TabRecoveryService {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
||||||
app: AppService,
|
private app: AppService,
|
||||||
log: LogService
|
log: LogService
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('tabRecovery')
|
this.logger = log.create('tabRecovery')
|
||||||
@@ -29,15 +29,22 @@ export class TabRecoveryService {
|
|||||||
|
|
||||||
async recoverTabs (): Promise<void> {
|
async recoverTabs (): Promise<void> {
|
||||||
if (window.localStorage.tabsRecovery) {
|
if (window.localStorage.tabsRecovery) {
|
||||||
|
let tabs: RecoveredTab[] = []
|
||||||
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
|
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
|
||||||
for (let provider of this.tabRecoveryProviders) {
|
for (let provider of this.tabRecoveryProviders) {
|
||||||
try {
|
try {
|
||||||
await provider.recover(token)
|
let tab = await provider.recover(token)
|
||||||
|
if (tab) {
|
||||||
|
tabs.push(tab)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn('Tab recovery crashed:', token, provider, error)
|
this.logger.warn('Tab recovery crashed:', token, provider, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
this.app.openNewTab(tab.type, tab.options)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,7 +13,6 @@ export class ThemesService {
|
|||||||
this.applyCurrentTheme()
|
this.applyCurrentTheme()
|
||||||
config.changed$.subscribe(() => {
|
config.changed$.subscribe(() => {
|
||||||
this.applyCurrentTheme()
|
this.applyCurrentTheme()
|
||||||
document.querySelector('style#custom-css').innerHTML = config.store.appearance.css
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +31,7 @@ export class ThemesService {
|
|||||||
document.querySelector('head').appendChild(this.styleElement)
|
document.querySelector('head').appendChild(this.styleElement)
|
||||||
}
|
}
|
||||||
this.styleElement.textContent = theme.css
|
this.styleElement.textContent = theme.css
|
||||||
|
document.querySelector('style#custom-css').innerHTML = this.config.store.appearance.css
|
||||||
}
|
}
|
||||||
|
|
||||||
applyCurrentTheme (): void {
|
applyCurrentTheme (): void {
|
||||||
|
@@ -23,6 +23,7 @@ $body-color: #aaa;
|
|||||||
$font-family-sans-serif: "Source Sans Pro";
|
$font-family-sans-serif: "Source Sans Pro";
|
||||||
$font-size-base: 14rem / 16;
|
$font-size-base: 14rem / 16;
|
||||||
|
|
||||||
|
$btn-border-radius: 0;
|
||||||
$btn-secondary-color: #ccc;
|
$btn-secondary-color: #ccc;
|
||||||
$btn-secondary-bg: #222;
|
$btn-secondary-bg: #222;
|
||||||
$btn-secondary-border: #444;
|
$btn-secondary-border: #444;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-plugin-manager",
|
"name": "terminus-plugin-manager",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0-alpha.16-8-gfc060ac",
|
||||||
"description": "Terminus' plugin manager",
|
"description": "Terminus' plugin manager",
|
||||||
|
"keywords": [
|
||||||
|
"terminus-plugin"
|
||||||
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -16,10 +19,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mz": "0.0.31",
|
"@types/mz": "0.0.31",
|
||||||
"@types/node": "7.0.12",
|
"@types/node": "7.0.12",
|
||||||
"@types/semver": "^5.3.31",
|
"@types/semver": "^5.3.32",
|
||||||
"@types/webpack-env": "1.13.0",
|
"@types/webpack-env": "1.13.0",
|
||||||
"ngx-pipes": "^1.6.1",
|
|
||||||
"css-loader": "^0.28.0",
|
"css-loader": "^0.28.0",
|
||||||
|
"ngx-pipes": "^1.6.1",
|
||||||
"semver": "^5.3.0"
|
"semver": "^5.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -33,7 +36,8 @@
|
|||||||
"rxjs": "5.3.0"
|
"rxjs": "5.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.16.2"
|
"axios": "^0.16.2",
|
||||||
|
"mz": "^2.6.0"
|
||||||
},
|
},
|
||||||
"false": {}
|
"false": {}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,9 @@ h3 Installed
|
|||||||
.mr-auto.d-flex.flex-column
|
.mr-auto.d-flex.flex-column
|
||||||
strong {{plugin.name}}
|
strong {{plugin.name}}
|
||||||
small.text-muted.mb-0 {{plugin.description}}
|
small.text-muted.mb-0 {{plugin.description}}
|
||||||
p.mb-0.mr-3 {{plugin.version}}
|
.d-flex.flex-column.align-items-end.mr-3
|
||||||
|
div {{plugin.version}}
|
||||||
|
small.text-muted {{plugin.author}}
|
||||||
button.btn.btn-outline-primary(
|
button.btn.btn-outline-primary(
|
||||||
*ngIf='npmInstalled',
|
*ngIf='npmInstalled',
|
||||||
(click)='upgradePlugin(plugin)',
|
(click)='upgradePlugin(plugin)',
|
||||||
@@ -31,7 +33,10 @@ h3 Installed
|
|||||||
.mr-auto.d-flex.flex-column
|
.mr-auto.d-flex.flex-column
|
||||||
strong {{plugin.name}}
|
strong {{plugin.name}}
|
||||||
small.text-muted.mb-0 {{plugin.description}}
|
small.text-muted.mb-0 {{plugin.description}}
|
||||||
p.mb-0.mr-3 {{plugin.version}}
|
.d-flex.flex-column.align-items-end.mr-3
|
||||||
|
div {{plugin.version}}
|
||||||
|
small.text-muted {{plugin.author}}
|
||||||
|
i.fa.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
|
||||||
button.btn.btn-outline-danger(
|
button.btn.btn-outline-danger(
|
||||||
(click)='uninstallPlugin(plugin)',
|
(click)='uninstallPlugin(plugin)',
|
||||||
*ngIf='!plugin.isBuiltin && npmInstalled',
|
*ngIf='!plugin.isBuiltin && npmInstalled',
|
||||||
@@ -41,12 +46,12 @@ h3 Installed
|
|||||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
|
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
|
||||||
|
|
||||||
.text-center.mt-5(*ngIf='npmMissing')
|
.text-center.mt-5(*ngIf='npmMissing')
|
||||||
h4 NPM not installed
|
h4 npm not installed
|
||||||
p.mb-2 The Node Package Manager is required to install Terminus plugins.
|
p.mb-2 npm is required to install Terminus plugins.
|
||||||
.btn-group
|
.btn-group
|
||||||
button.btn.btn-outline-primary((click)='downloadNPM()')
|
button.btn.btn-outline-primary((click)='downloadNPM()')
|
||||||
i.fa.fa-download
|
i.fa.fa-download
|
||||||
span Download NPM
|
span Get npm
|
||||||
button.btn.btn-outline-info((click)='checkNPM()')
|
button.btn.btn-outline-info((click)='checkNPM()')
|
||||||
i.fa.fa-refresh
|
i.fa.fa-refresh
|
||||||
span Try again
|
span Try again
|
||||||
@@ -73,7 +78,10 @@ div(*ngIf='npmInstalled')
|
|||||||
.mr-auto.d-flex.flex-column
|
.mr-auto.d-flex.flex-column
|
||||||
strong {{plugin.name}}
|
strong {{plugin.name}}
|
||||||
small.text-muted.mb-0 {{plugin.description}}
|
small.text-muted.mb-0 {{plugin.description}}
|
||||||
p.mb-0.mr-3 {{plugin.version}}
|
.d-flex.flex-column.align-items-end.mr-3
|
||||||
|
div {{plugin.version}}
|
||||||
|
small.text-muted {{plugin.author}}
|
||||||
|
i.fa.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
|
||||||
button.btn.btn-outline-primary(
|
button.btn.btn-outline-primary(
|
||||||
(click)='installPlugin(plugin)',
|
(click)='installPlugin(plugin)',
|
||||||
[disabled]='busy[plugin.name] != undefined'
|
[disabled]='busy[plugin.name] != undefined'
|
||||||
|
7
terminus-plugin-manager/src/config.ts
Normal file
7
terminus-plugin-manager/src/config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { ConfigProvider } from 'terminus-core'
|
||||||
|
|
||||||
|
export class PluginsConfigProvider extends ConfigProvider {
|
||||||
|
defaults = {
|
||||||
|
npm: 'npm',
|
||||||
|
}
|
||||||
|
}
|
@@ -4,10 +4,12 @@ import { FormsModule } from '@angular/forms'
|
|||||||
import { NgPipesModule } from 'ngx-pipes'
|
import { NgPipesModule } from 'ngx-pipes'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
|
import { ConfigProvider } from 'terminus-core'
|
||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
|
||||||
import { PluginsSettingsTabComponent } from './components/pluginsSettingsTab.component'
|
import { PluginsSettingsTabComponent } from './components/pluginsSettingsTab.component'
|
||||||
import { PluginManagerService } from './services/pluginManager.service'
|
import { PluginManagerService } from './services/pluginManager.service'
|
||||||
|
import { PluginsConfigProvider } from './config'
|
||||||
import { PluginsSettingsTabProvider } from './settings'
|
import { PluginsSettingsTabProvider } from './settings'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -19,6 +21,7 @@ import { PluginsSettingsTabProvider } from './settings'
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SettingsTabProvider, useClass: PluginsSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: PluginsSettingsTabProvider, multi: true },
|
||||||
|
{ provide: ConfigProvider, useClass: PluginsConfigProvider, multi: true },
|
||||||
PluginManagerService,
|
PluginManagerService,
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
|
@@ -1,19 +1,24 @@
|
|||||||
import { Observable } from 'rxjs'
|
import * as path from 'path'
|
||||||
import { Injectable } from '@angular/core'
|
import * as fs from 'mz/fs'
|
||||||
import { Logger, LogService } from 'terminus-core'
|
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { Observable } from 'rxjs'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { Logger, LogService, ConfigService, HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
const NAME_PREFIX = 'terminus-'
|
const NAME_PREFIX = 'terminus-'
|
||||||
const KEYWORD = 'terminus-plugin'
|
const KEYWORD = 'terminus-plugin'
|
||||||
|
const OFFICIAL_NPM_ACCOUNT = 'eugenepankov'
|
||||||
|
|
||||||
export interface IPluginInfo {
|
export interface IPluginInfo {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
packageName: string
|
packageName: string
|
||||||
isBuiltin: boolean
|
isBuiltin: boolean
|
||||||
|
isOfficial: boolean
|
||||||
version: string
|
version: string
|
||||||
homepage?: string
|
homepage?: string
|
||||||
|
author: string
|
||||||
path?: string
|
path?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,17 +28,38 @@ export class PluginManagerService {
|
|||||||
builtinPluginsPath: string = (window as any).builtinPluginsPath
|
builtinPluginsPath: string = (window as any).builtinPluginsPath
|
||||||
userPluginsPath: string = (window as any).userPluginsPath
|
userPluginsPath: string = (window as any).userPluginsPath
|
||||||
installedPlugins: IPluginInfo[] = (window as any).installedPlugins
|
installedPlugins: IPluginInfo[] = (window as any).installedPlugins
|
||||||
npmBinary = 'npm'
|
npmPath: string
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
log: LogService,
|
log: LogService,
|
||||||
|
private config: ConfigService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('pluginManager')
|
this.logger = log.create('pluginManager')
|
||||||
|
this.detectPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
async detectPath () {
|
||||||
|
this.npmPath = this.config.store.npm
|
||||||
|
if (await fs.exists(this.npmPath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
|
let searchPaths = (await exec('bash -c -l "echo $PATH"'))[0].toString().trim().split(':')
|
||||||
|
for (let searchPath of searchPaths) {
|
||||||
|
if (await fs.exists(path.join(searchPath, 'npm'))) {
|
||||||
|
this.logger.debug('Found npm in', searchPath)
|
||||||
|
this.npmPath = path.join(searchPath, 'npm')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async isNPMInstalled (): Promise<boolean> {
|
async isNPMInstalled (): Promise<boolean> {
|
||||||
|
await this.detectPath()
|
||||||
try {
|
try {
|
||||||
await exec(`${this.npmBinary} -v`)
|
await exec(`${this.npmPath} -v`)
|
||||||
return true
|
return true
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false
|
return false
|
||||||
@@ -45,25 +71,28 @@ export class PluginManagerService {
|
|||||||
.fromPromise(
|
.fromPromise(
|
||||||
axios.get(`https://www.npmjs.com/-/search?text=keywords:${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=1000`)
|
axios.get(`https://www.npmjs.com/-/search?text=keywords:${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=1000`)
|
||||||
)
|
)
|
||||||
|
.do(response => console.log(response.data.objects))
|
||||||
.map(response => response.data.objects.map(item => ({
|
.map(response => response.data.objects.map(item => ({
|
||||||
name: item.package.name.substring(NAME_PREFIX.length),
|
name: item.package.name.substring(NAME_PREFIX.length),
|
||||||
packageName: item.package.name,
|
packageName: item.package.name,
|
||||||
description: item.package.description,
|
description: item.package.description,
|
||||||
version: item.package.version,
|
version: item.package.version,
|
||||||
homepage: item.package.links.homepage,
|
homepage: item.package.links.homepage,
|
||||||
|
author: (item.package.author || {}).name,
|
||||||
|
isOfficial: item.package.publisher.username === OFFICIAL_NPM_ACCOUNT,
|
||||||
})))
|
})))
|
||||||
.map(plugins => plugins.filter(x => x.packageName.startsWith(NAME_PREFIX)))
|
.map(plugins => plugins.filter(x => x.packageName.startsWith(NAME_PREFIX)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async installPlugin (plugin: IPluginInfo) {
|
async installPlugin (plugin: IPluginInfo) {
|
||||||
let result = await exec(`${this.npmBinary} --prefix "${this.userPluginsPath}" install ${plugin.packageName}@${plugin.version}`)
|
let result = await exec(`${this.npmPath} --prefix "${this.userPluginsPath}" install ${plugin.packageName}@${plugin.version}`)
|
||||||
console.log(result)
|
console.log(result)
|
||||||
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
|
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
|
||||||
this.installedPlugins.push(plugin)
|
this.installedPlugins.push(plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstallPlugin (plugin: IPluginInfo) {
|
async uninstallPlugin (plugin: IPluginInfo) {
|
||||||
await exec(`${this.npmBinary} --prefix "${this.userPluginsPath}" remove ${plugin.packageName}`)
|
await exec(`${this.npmPath} --prefix "${this.userPluginsPath}" remove ${plugin.packageName}`)
|
||||||
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
|
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,10 +37,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
'fs',
|
'fs',
|
||||||
'fs-promise',
|
|
||||||
'font-manager',
|
'font-manager',
|
||||||
'path',
|
'path',
|
||||||
'node-pty',
|
'node-pty',
|
||||||
|
'mz/fs',
|
||||||
'mz/child_process',
|
'mz/child_process',
|
||||||
'winreg',
|
'winreg',
|
||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-settings",
|
"name": "terminus-settings",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0-alpha.16-8-gfc060ac",
|
||||||
"description": "Terminus terminal settings page",
|
"description": "Terminus terminal settings page",
|
||||||
|
"keywords": [
|
||||||
|
"terminus-plugin"
|
||||||
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { ToolbarButtonProvider, IToolbarButton, AppService } from 'terminus-core'
|
import { ToolbarButtonProvider, IToolbarButton, AppService, HostAppService } from 'terminus-core'
|
||||||
|
|
||||||
import { SettingsTabComponent } from './components/settingsTab.component'
|
import { SettingsTabComponent } from './components/settingsTab.component'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ButtonProvider extends ToolbarButtonProvider {
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
constructor (
|
constructor (
|
||||||
|
hostApp: HostAppService,
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
hostApp.preferencesMenu$.subscribe(() => this.open())
|
||||||
}
|
}
|
||||||
|
|
||||||
provide (): IToolbarButton[] {
|
provide (): IToolbarButton[] {
|
||||||
@@ -16,14 +18,16 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
icon: 'sliders',
|
icon: 'sliders',
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
weight: 10,
|
weight: 10,
|
||||||
click: () => {
|
click: () => this.open(),
|
||||||
let settingsTab = this.app.tabs.find((tab) => tab instanceof SettingsTabComponent)
|
|
||||||
if (settingsTab) {
|
|
||||||
this.app.selectTab(settingsTab)
|
|
||||||
} else {
|
|
||||||
this.app.openNewTab(SettingsTabComponent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open (): void {
|
||||||
|
let settingsTab = this.app.tabs.find((tab) => tab instanceof SettingsTabComponent)
|
||||||
|
if (settingsTab) {
|
||||||
|
this.app.selectTab(settingsTab)
|
||||||
|
} else {
|
||||||
|
this.app.openNewTab(SettingsTabComponent)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -73,6 +73,7 @@ export class HotkeyInputModalComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
this.hotkeys.clearCurrentKeystrokes()
|
||||||
this.hotkeys.enable()
|
this.hotkeys.enable()
|
||||||
clearInterval(this.keyTimeoutInterval)
|
clearInterval(this.keyTimeoutInterval)
|
||||||
}
|
}
|
||||||
|
@@ -2,9 +2,9 @@ button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(clic
|
|||||||
|
|
||||||
ngb-tabset.vertical(type='tabs')
|
ngb-tabset.vertical(type='tabs')
|
||||||
ngb-tab
|
ngb-tab
|
||||||
template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
| Application
|
| Application
|
||||||
template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
.row
|
.row
|
||||||
.col.col-lg-6
|
.col.col-lg-6
|
||||||
.form-group
|
.form-group
|
||||||
@@ -109,17 +109,17 @@ ngb-tabset.vertical(type='tabs')
|
|||||||
label Display on
|
label Display on
|
||||||
br
|
br
|
||||||
div(
|
div(
|
||||||
'[(ngModel)]'='config.store.appearance.dockScreen'
|
[(ngModel)]='config.store.appearance.dockScreen',
|
||||||
'(ngModelChange)'='config.save(); docking.dock()'
|
(ngModelChange)='config.save(); docking.dock()',
|
||||||
ngbRadioGroup
|
ngbRadioGroup
|
||||||
)
|
)
|
||||||
label.btn.btn-secondary
|
label.btn.btn-secondary
|
||||||
input(
|
input(
|
||||||
type='radio',
|
type='radio',
|
||||||
[value]='"current"'
|
value='current'
|
||||||
)
|
)
|
||||||
| Current
|
| Current
|
||||||
label.btn.btn-secondary(*ngFor='let screen of docking.getScreens()')
|
label.btn.btn-secondary(*ngFor='let screen of screens')
|
||||||
input(
|
input(
|
||||||
type='radio',
|
type='radio',
|
||||||
[value]='screen.id'
|
[value]='screen.id'
|
||||||
@@ -153,9 +153,9 @@ ngb-tabset.vertical(type='tabs')
|
|||||||
)
|
)
|
||||||
|
|
||||||
ngb-tab
|
ngb-tab
|
||||||
template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
| Hotkeys
|
| Hotkeys
|
||||||
template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
||||||
.form-group
|
.form-group
|
||||||
table.hotkeys-table
|
table.hotkeys-table
|
||||||
@@ -173,7 +173,7 @@ ngb-tabset.vertical(type='tabs')
|
|||||||
)
|
)
|
||||||
|
|
||||||
ngb-tab(*ngFor='let provider of settingsProviders')
|
ngb-tab(*ngFor='let provider of settingsProviders')
|
||||||
template(ngbTabTitle)
|
ng-template(ngbTabTitle)
|
||||||
| {{provider.title}}
|
| {{provider.title}}
|
||||||
template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
settings-tab-body([provider]='provider')
|
settings-tab-body([provider]='provider')
|
||||||
|
@@ -14,6 +14,7 @@ import { SettingsTabProvider } from '../api'
|
|||||||
export class SettingsTabComponent extends BaseTabComponent {
|
export class SettingsTabComponent extends BaseTabComponent {
|
||||||
hotkeyFilter = ''
|
hotkeyFilter = ''
|
||||||
private hotkeyDescriptions: IHotkeyDescription[]
|
private hotkeyDescriptions: IHotkeyDescription[]
|
||||||
|
private screens
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
@@ -26,8 +27,9 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
||||||
this.title$.next('Settings')
|
this.title = 'Settings'
|
||||||
this.scrollable = true
|
this.scrollable = true
|
||||||
|
this.screens = this.docking.getScreens()
|
||||||
}
|
}
|
||||||
|
|
||||||
getRecoveryToken (): any {
|
getRecoveryToken (): any {
|
||||||
|
@@ -1,19 +1,14 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TabRecoveryProvider, AppService } from 'terminus-core'
|
import { TabRecoveryProvider, RecoveredTab } from 'terminus-core'
|
||||||
|
|
||||||
import { SettingsTabComponent } from './components/settingsTab.component'
|
import { SettingsTabComponent } from './components/settingsTab.component'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RecoveryProvider extends TabRecoveryProvider {
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
constructor (
|
async recover (recoveryToken: any): Promise<RecoveredTab> {
|
||||||
private app: AppService
|
|
||||||
) {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
async recover (recoveryToken: any): Promise<void> {
|
|
||||||
if (recoveryToken.type === 'app:settings') {
|
if (recoveryToken.type === 'app:settings') {
|
||||||
this.app.openNewTab(SettingsTabComponent)
|
return { type: SettingsTabComponent }
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -38,10 +38,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
'fs',
|
'fs',
|
||||||
'fs-promise',
|
|
||||||
'path',
|
'path',
|
||||||
'node-pty',
|
'node-pty',
|
||||||
'fs-promise',
|
|
||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
/^@angular/,
|
/^@angular/,
|
||||||
/^@ng-bootstrap/,
|
/^@ng-bootstrap/,
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-terminal",
|
"name": "terminus-terminal",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0-alpha.16-8-gfc060ac",
|
||||||
"description": "Terminus' terminal emulation core",
|
"description": "Terminus' terminal emulation core",
|
||||||
|
"keywords": [
|
||||||
|
"terminus-plugin"
|
||||||
|
],
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -20,7 +23,8 @@
|
|||||||
"@types/webpack-env": "1.13.0",
|
"@types/webpack-env": "1.13.0",
|
||||||
"@types/winreg": "^1.2.30",
|
"@types/winreg": "^1.2.30",
|
||||||
"dataurl": "0.1.0",
|
"dataurl": "0.1.0",
|
||||||
"deep-equal": "1.0.1"
|
"deep-equal": "1.0.1",
|
||||||
|
"file-loader": "^0.11.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "4.0.1",
|
"@angular/common": "4.0.1",
|
||||||
@@ -28,16 +32,19 @@
|
|||||||
"@angular/forms": "4.0.1",
|
"@angular/forms": "4.0.1",
|
||||||
"@angular/platform-browser": "4.0.1",
|
"@angular/platform-browser": "4.0.1",
|
||||||
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22",
|
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22",
|
||||||
|
"rxjs": "5.3.0",
|
||||||
"terminus-core": "*",
|
"terminus-core": "*",
|
||||||
"terminus-settings": "*",
|
"terminus-settings": "*"
|
||||||
"rxjs": "5.3.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/async-lock": "0.0.19",
|
||||||
|
"async-lock": "^1.0.0",
|
||||||
"font-manager": "0.2.2",
|
"font-manager": "0.2.2",
|
||||||
"fs-promise": "2.0.2",
|
|
||||||
"hterm-umdjs": "1.1.3",
|
"hterm-umdjs": "1.1.3",
|
||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
"node-pty": "0.6.2",
|
"node-pty": "0.6.8",
|
||||||
|
"ps-node": "^0.1.6",
|
||||||
|
"runes": "^0.4.2",
|
||||||
"winreg": "^1.2.3"
|
"winreg": "^1.2.3"
|
||||||
},
|
},
|
||||||
"false": {}
|
"false": {}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
export { TerminalTabComponent }
|
export { TerminalTabComponent }
|
||||||
|
export { IChildProcess } from './services/sessions.service'
|
||||||
|
|
||||||
export abstract class TerminalDecorator {
|
export abstract class TerminalDecorator {
|
||||||
// tslint:disable-next-line no-empty
|
// tslint:disable-next-line no-empty
|
||||||
|
BIN
terminus-terminal/src/bell.ogg
Normal file
BIN
terminus-terminal/src/bell.ogg
Normal file
Binary file not shown.
@@ -1,8 +1,10 @@
|
|||||||
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, AppService, ConfigService, ElectronService } from 'terminus-core'
|
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, AppService, ConfigService, HostAppService, Platform, ElectronService } from 'terminus-core'
|
||||||
|
|
||||||
import { SessionsService } from './services/sessions.service'
|
import { SessionsService } from './services/sessions.service'
|
||||||
|
import { ShellsService } from './services/shells.service'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -11,7 +13,9 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
private app: AppService,
|
private app: AppService,
|
||||||
private sessions: SessionsService,
|
private sessions: SessionsService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private electron: ElectronService,
|
private shells: ShellsService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
electron: ElectronService,
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@@ -20,30 +24,50 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
this.openNewTab()
|
this.openNewTab()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
hostApp.secondInstance$.subscribe(async ({argv, cwd}) => {
|
||||||
|
if (argv.length === 2) {
|
||||||
|
let arg = path.resolve(cwd, argv[1])
|
||||||
|
if (await fs.exists(arg)) {
|
||||||
|
this.openNewTab(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!electron.remote.process.env.DEV) {
|
||||||
|
setImmediate(async () => {
|
||||||
|
let argv: string[] = electron.remote.process.argv
|
||||||
|
for (let arg of argv.slice(1).concat([electron.remote.process.argv0])) {
|
||||||
|
if (await fs.exists(arg)) {
|
||||||
|
if ((await fs.stat(arg)).isDirectory()) {
|
||||||
|
this.openNewTab(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async openNewTab (): Promise<void> {
|
async openNewTab (cwd?: string): Promise<void> {
|
||||||
let cwd = null
|
if (!cwd && this.app.activeTab instanceof TerminalTabComponent) {
|
||||||
if (this.app.activeTab instanceof TerminalTabComponent) {
|
|
||||||
cwd = await this.app.activeTab.session.getWorkingDirectory()
|
cwd = await this.app.activeTab.session.getWorkingDirectory()
|
||||||
}
|
}
|
||||||
let command = this.config.store.terminal.shell
|
let command = this.config.store.terminal.shell
|
||||||
let args = []
|
let env: any = process.env
|
||||||
// TODO move this?
|
let args: string[] = []
|
||||||
if (command === '~clink~') {
|
if (command === '~clink~') {
|
||||||
command = 'cmd.exe'
|
({ command, args } = this.shells.getClinkOptions())
|
||||||
args = [
|
|
||||||
'/k',
|
|
||||||
path.join(
|
|
||||||
path.dirname(this.electron.app.getPath('exe')),
|
|
||||||
(process.platform === 'darwin') ? '../Resources' : 'resources',
|
|
||||||
'clink',
|
|
||||||
`clink_${process.arch}.exe`,
|
|
||||||
),
|
|
||||||
'inject',
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
let sessionOptions = await this.sessions.prepareNewSession({ command, args, cwd })
|
if (command === '~default-shell~') {
|
||||||
|
command = await this.shells.getDefaultShell()
|
||||||
|
}
|
||||||
|
if (this.hostApp.platform === Platform.Windows) {
|
||||||
|
env.TERM = 'cygwin'
|
||||||
|
}
|
||||||
|
let sessionOptions = await this.sessions.prepareNewSession({
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
cwd,
|
||||||
|
env,
|
||||||
|
})
|
||||||
this.app.openNewTab(
|
this.app.openNewTab(
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
{ sessionOptions }
|
{ sessionOptions }
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import * as fs from 'fs-promise'
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TerminalColorSchemeProvider, ITerminalColorScheme } from './api'
|
import { TerminalColorSchemeProvider, ITerminalColorScheme } from './api'
|
||||||
@@ -6,7 +6,7 @@ import { TerminalColorSchemeProvider, ITerminalColorScheme } from './api'
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class HyperColorSchemes extends TerminalColorSchemeProvider {
|
export class HyperColorSchemes extends TerminalColorSchemeProvider {
|
||||||
async getSchemes (): Promise<ITerminalColorScheme[]> {
|
async getSchemes (): Promise<ITerminalColorScheme[]> {
|
||||||
let pluginsPath = path.join(process.env.HOME || (process.env.HOMEDRIVE + process.env.HOMEPATH), '.hyper_plugins', 'node_modules')
|
let pluginsPath = path.join(process.env.HOME, '.hyper_plugins', 'node_modules')
|
||||||
if (!(await fs.exists(pluginsPath))) return []
|
if (!(await fs.exists(pluginsPath))) return []
|
||||||
let plugins = await fs.readdir(pluginsPath)
|
let plugins = await fs.readdir(pluginsPath)
|
||||||
|
|
||||||
|
@@ -174,26 +174,53 @@
|
|||||||
[title]='idx',
|
[title]='idx',
|
||||||
)
|
)
|
||||||
|
|
||||||
.form-group
|
.d-flex
|
||||||
label Terminal background
|
.form-group.mr-3
|
||||||
br
|
label Terminal background
|
||||||
div(
|
br
|
||||||
'[(ngModel)]'='config.store.terminal.background',
|
div(
|
||||||
(ngModelChange)='config.save()',
|
'[(ngModel)]'='config.store.terminal.background',
|
||||||
ngbRadioGroup
|
(ngModelChange)='config.save()',
|
||||||
)
|
ngbRadioGroup
|
||||||
label.btn.btn-secondary
|
)
|
||||||
input(
|
label.btn.btn-secondary
|
||||||
type='radio',
|
input(
|
||||||
[value]='"theme"'
|
type='radio',
|
||||||
)
|
[value]='"theme"'
|
||||||
| From theme
|
)
|
||||||
label.btn.btn-secondary
|
| From theme
|
||||||
input(
|
label.btn.btn-secondary
|
||||||
type='radio',
|
input(
|
||||||
[value]='"colorScheme"'
|
type='radio',
|
||||||
)
|
[value]='"colorScheme"'
|
||||||
| From colors
|
)
|
||||||
|
| From colors
|
||||||
|
.form-group
|
||||||
|
label Cursor shape
|
||||||
|
br
|
||||||
|
div(
|
||||||
|
[(ngModel)]='config.store.terminal.cursor',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
ngbRadioGroup
|
||||||
|
)
|
||||||
|
label.btn.btn-secondary
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
[value]='"block"'
|
||||||
|
)
|
||||||
|
| Block
|
||||||
|
label.btn.btn-secondary
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
[value]='"underline"'
|
||||||
|
)
|
||||||
|
| Underline
|
||||||
|
label.btn.btn-secondary
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
[value]='"beam"'
|
||||||
|
)
|
||||||
|
| Beam
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Shell
|
label Shell
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import * as fs from 'fs-promise'
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
const equal = require('deep-equal')
|
const equal = require('deep-equal')
|
||||||
@@ -59,16 +59,19 @@ export class TerminalSettingsTabComponent {
|
|||||||
{ name: 'CMD (stock)', command: 'cmd.exe' },
|
{ name: 'CMD (stock)', command: 'cmd.exe' },
|
||||||
{ name: 'PowerShell', command: 'powershell.exe' },
|
{ name: 'PowerShell', command: 'powershell.exe' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Detect whether BoW is installed
|
||||||
const wslPath = `${process.env.windir}\\system32\\bash.exe`
|
const wslPath = `${process.env.windir}\\system32\\bash.exe`
|
||||||
if (await fs.exists(wslPath)) {
|
if (await fs.exists(wslPath)) {
|
||||||
this.shells.push({ name: 'Bash on Windows', command: wslPath })
|
this.shells.push({ name: 'Bash on Windows', command: wslPath })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect Cygwin
|
||||||
let cygwinPath = await new Promise<string>(resolve => {
|
let cygwinPath = await new Promise<string>(resolve => {
|
||||||
let reg = new Registry({ hive: Registry.HKLM, key: '\\Software\\Cygwin\\setup' })
|
let reg = new Registry({ hive: Registry.HKLM, key: '\\Software\\Cygwin\\setup', arch: 'x64' })
|
||||||
reg.get('rootdir', (err, item) => {
|
reg.get('rootdir', (err, item) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
resolve(null)
|
return resolve(null)
|
||||||
}
|
}
|
||||||
resolve(item.value)
|
resolve(item.value)
|
||||||
})
|
})
|
||||||
@@ -76,13 +79,43 @@ export class TerminalSettingsTabComponent {
|
|||||||
if (cygwinPath) {
|
if (cygwinPath) {
|
||||||
this.shells.push({ name: 'Cygwin', command: path.join(cygwinPath, 'bin', 'bash.exe') })
|
this.shells.push({ name: 'Cygwin', command: path.join(cygwinPath, 'bin', 'bash.exe') })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect 32-bit Cygwin
|
||||||
|
let cygwin32Path = await new Promise<string>(resolve => {
|
||||||
|
let reg = new Registry({ hive: Registry.HKLM, key: '\\Software\\Cygwin\\setup', arch: 'x86' })
|
||||||
|
reg.get('rootdir', (err, item) => {
|
||||||
|
if (err) {
|
||||||
|
return resolve(null)
|
||||||
|
}
|
||||||
|
resolve(item.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (cygwin32Path) {
|
||||||
|
this.shells.push({ name: 'Cygwin (32 bit)', command: path.join(cygwin32Path, 'bin', 'bash.exe') })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect Git-Bash
|
||||||
|
let gitBashPath = await new Promise<string>(resolve => {
|
||||||
|
let reg = new Registry({ hive: Registry.HKLM, key: '\\Software\\GitForWindows' })
|
||||||
|
reg.get('InstallPath', (err, item) => {
|
||||||
|
if (err) {
|
||||||
|
resolve(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(item.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (gitBashPath) {
|
||||||
|
this.shells.push({ name: 'Git-Bash', command: path.join(gitBashPath, 'bin', 'bash.exe') })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.hostApp.platform === Platform.Linux || this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.Linux || this.hostApp.platform === Platform.macOS) {
|
||||||
this.shells = (await fs.readFile('/etc/shells', 'utf-8'))
|
this.shells = [{ name: 'Default shell', command: '~default-shell~' }]
|
||||||
|
this.shells = this.shells.concat((await fs.readFile('/etc/shells', { encoding: 'utf-8' }))
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(x => x.trim())
|
.map(x => x.trim())
|
||||||
.filter(x => x && !x.startsWith('#'))
|
.filter(x => x && !x.startsWith('#'))
|
||||||
.map(x => ({ name: x, command: x }))
|
.map(x => ({ name: x, command: x })))
|
||||||
}
|
}
|
||||||
this.colorSchemes = (await Promise.all(this.colorSchemeProviders.map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
|
this.colorSchemes = (await Promise.all(this.colorSchemeProviders.map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 15px;
|
margin: 15px;
|
||||||
transition: opacity ease-out 0.1s;
|
transition: opacity ease-out 0.25s;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
div[style]:last-child {
|
div[style]:last-child {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { BehaviorSubject, ReplaySubject, Subject, Subscription } from 'rxjs'
|
import { BehaviorSubject, Subject, Subscription } from 'rxjs'
|
||||||
import 'rxjs/add/operator/bufferTime'
|
import 'rxjs/add/operator/bufferTime'
|
||||||
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
||||||
import { AppService, ConfigService, BaseTabComponent, ThemesService, HostAppService, Platform } from 'terminus-core'
|
import { AppService, ConfigService, BaseTabComponent, ThemesService, HostAppService, HotkeysService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
import { Session, SessionsService } from '../services/sessions.service'
|
import { Session, SessionsService } from '../services/sessions.service'
|
||||||
|
|
||||||
@@ -16,13 +16,14 @@ import { hterm, preferenceManager } from '../hterm'
|
|||||||
export class TerminalTabComponent extends BaseTabComponent {
|
export class TerminalTabComponent extends BaseTabComponent {
|
||||||
session: Session
|
session: Session
|
||||||
@Input() sessionOptions: SessionOptions
|
@Input() sessionOptions: SessionOptions
|
||||||
|
@Input() zoom = 0
|
||||||
@ViewChild('content') content
|
@ViewChild('content') content
|
||||||
@HostBinding('style.background-color') backgroundColor: string
|
@HostBinding('style.background-color') backgroundColor: string
|
||||||
hterm: any
|
hterm: any
|
||||||
configSubscription: Subscription
|
|
||||||
sessionCloseSubscription: Subscription
|
sessionCloseSubscription: Subscription
|
||||||
|
hotkeysSubscription: Subscription
|
||||||
bell$ = new Subject()
|
bell$ = new Subject()
|
||||||
size$ = new ReplaySubject<ResizeEvent>(1)
|
size: ResizeEvent
|
||||||
resize$ = new Subject<ResizeEvent>()
|
resize$ = new Subject<ResizeEvent>()
|
||||||
input$ = new Subject<string>()
|
input$ = new Subject<string>()
|
||||||
output$ = new Subject<string>()
|
output$ = new Subject<string>()
|
||||||
@@ -30,6 +31,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
alternateScreenActive$ = new BehaviorSubject(false)
|
alternateScreenActive$ = new BehaviorSubject(false)
|
||||||
mouseEvent$ = new Subject<Event>()
|
mouseEvent$ = new Subject<Event>()
|
||||||
htermVisible = false
|
htermVisible = false
|
||||||
|
private bellPlayer: HTMLAudioElement
|
||||||
private io: any
|
private io: any
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@@ -37,20 +39,21 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
private app: AppService,
|
private app: AppService,
|
||||||
private themes: ThemesService,
|
private themes: ThemesService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
|
private hotkeys: HotkeysService,
|
||||||
private sessions: SessionsService,
|
private sessions: SessionsService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.decorators = this.decorators || []
|
this.decorators = this.decorators || []
|
||||||
this.title$.next('Terminal')
|
this.title = 'Terminal'
|
||||||
this.configSubscription = config.changed$.subscribe(() => {
|
|
||||||
this.configure()
|
|
||||||
})
|
|
||||||
this.resize$.first().subscribe(async (resizeEvent) => {
|
this.resize$.first().subscribe(async (resizeEvent) => {
|
||||||
this.session = this.sessions.addSession(
|
this.session = this.sessions.addSession(
|
||||||
Object.assign({}, this.sessionOptions, resizeEvent)
|
Object.assign({}, this.sessionOptions, resizeEvent)
|
||||||
)
|
)
|
||||||
|
setTimeout(() => {
|
||||||
|
this.session.resize(resizeEvent.width, resizeEvent.height)
|
||||||
|
}, 1000)
|
||||||
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
||||||
this.session.output$.subscribe(data => {
|
this.session.output$.subscribe(data => {
|
||||||
// let data = datas.join('')
|
// let data = datas.join('')
|
||||||
@@ -64,6 +67,28 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
})
|
})
|
||||||
this.session.releaseInitialDataBuffer()
|
this.session.releaseInitialDataBuffer()
|
||||||
})
|
})
|
||||||
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
|
if (!this.hasFocus) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (hotkey === 'copy') {
|
||||||
|
this.hterm.copySelectionToClipboard()
|
||||||
|
}
|
||||||
|
if (hotkey === 'clear') {
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
if (hotkey === 'zoom-in') {
|
||||||
|
this.zoomIn()
|
||||||
|
}
|
||||||
|
if (hotkey === 'zoom-out') {
|
||||||
|
this.zoomOut()
|
||||||
|
}
|
||||||
|
if (hotkey === 'reset-zoom') {
|
||||||
|
this.resetZoom()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.bellPlayer = document.createElement('audio')
|
||||||
|
this.bellPlayer.src = require<string>('../bell.ogg')
|
||||||
}
|
}
|
||||||
|
|
||||||
getRecoveryToken (): any {
|
getRecoveryToken (): any {
|
||||||
@@ -75,6 +100,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
|
this.configure()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.hterm.scrollPort_.resize()
|
this.hterm.scrollPort_.resize()
|
||||||
this.hterm.scrollPort_.focus()
|
this.hterm.scrollPort_.focus()
|
||||||
@@ -105,13 +131,15 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
this.bell$.subscribe(() => {
|
this.bell$.subscribe(() => {
|
||||||
if (this.config.store.terminal.bell !== 'off') {
|
if (this.config.store.terminal.bell === 'visual') {
|
||||||
let bg = preferenceManager.get('background-color')
|
|
||||||
preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
preferenceManager.set('background-color', bg)
|
this.configure()
|
||||||
}, 125)
|
}, 125)
|
||||||
}
|
}
|
||||||
|
if (this.config.store.terminal.bell === 'audible') {
|
||||||
|
this.bellPlayer.play()
|
||||||
|
}
|
||||||
// TODO audible
|
// TODO audible
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -119,7 +147,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
attachHTermHandlers (hterm: any) {
|
attachHTermHandlers (hterm: any) {
|
||||||
hterm.setWindowTitle = (title) => {
|
hterm.setWindowTitle = (title) => {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this.title$.next(title)
|
this.title = title
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +159,8 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
|
|
||||||
hterm.primaryScreen_.syncSelectionCaret = () => null
|
hterm.primaryScreen_.syncSelectionCaret = () => null
|
||||||
hterm.alternateScreen_.syncSelectionCaret = () => null
|
hterm.alternateScreen_.syncSelectionCaret = () => null
|
||||||
|
hterm.primaryScreen_.terminal = hterm
|
||||||
|
hterm.alternateScreen_.terminal = hterm
|
||||||
|
|
||||||
const _onPaste = hterm.scrollPort_.onPaste_.bind(hterm.scrollPort_)
|
const _onPaste = hterm.scrollPort_.onPaste_.bind(hterm.scrollPort_)
|
||||||
hterm.scrollPort_.onPaste_ = (event) => {
|
hterm.scrollPort_.onPaste_ = (event) => {
|
||||||
@@ -150,10 +180,18 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
const _onMouse = hterm.onMouse_.bind(hterm)
|
const _onMouse = hterm.onMouse_.bind(hterm)
|
||||||
hterm.onMouse_ = (event) => {
|
hterm.onMouse_ = (event) => {
|
||||||
this.mouseEvent$.next(event)
|
this.mouseEvent$.next(event)
|
||||||
if ((event.ctrlKey || event.metaKey) && event.type === 'mousewheel') {
|
if (event.type === 'mousewheel') {
|
||||||
event.preventDefault()
|
if (event.ctrlKey || event.metaKey) {
|
||||||
let delta = Math.round(event.wheelDeltaY / 50)
|
if (event.wheelDeltaY < 0) {
|
||||||
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
this.zoomIn()
|
||||||
|
} else {
|
||||||
|
this.zoomOut()
|
||||||
|
}
|
||||||
|
} else if (event.altKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
let delta = Math.round(event.wheelDeltaY / 50)
|
||||||
|
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_onMouse(event)
|
_onMouse(event)
|
||||||
}
|
}
|
||||||
@@ -188,11 +226,11 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
io.onTerminalResize = (columns, rows) => {
|
io.onTerminalResize = (columns, rows) => {
|
||||||
// console.log(`Resizing to ${columns}x${rows}`)
|
// console.log(`Resizing to ${columns}x${rows}`)
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this.size$.next({ width: columns, height: rows })
|
this.size = { width: columns, height: rows }
|
||||||
this.resize$.next({ width: columns, height: rows })
|
|
||||||
if (this.session) {
|
if (this.session) {
|
||||||
this.session.resize(columns, rows)
|
this.session.resize(columns, rows)
|
||||||
}
|
}
|
||||||
|
this.resize$.next(this.size)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,12 +243,17 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
this.io.writeUTF8(data)
|
this.io.writeUTF8(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear () {
|
||||||
|
this.hterm.wipeContents()
|
||||||
|
this.hterm.onVTKeystroke('\f')
|
||||||
|
}
|
||||||
|
|
||||||
async configure (): Promise<void> {
|
async configure (): Promise<void> {
|
||||||
let config = this.config.store
|
let config = this.config.store
|
||||||
preferenceManager.set('font-family', config.terminal.font)
|
preferenceManager.set('font-family', `"${config.terminal.font}", "monospace-fallback", monospace`)
|
||||||
preferenceManager.set('font-size', config.terminal.fontSize)
|
this.setFontSize()
|
||||||
preferenceManager.set('enable-bold', true)
|
preferenceManager.set('enable-bold', true)
|
||||||
preferenceManager.set('audible-bell-sound', '')
|
// preferenceManager.set('audible-bell-sound', '')
|
||||||
preferenceManager.set('desktop-notification-bell', config.terminal.bell === 'notification')
|
preferenceManager.set('desktop-notification-bell', config.terminal.bell === 'notification')
|
||||||
preferenceManager.set('enable-clipboard-notice', false)
|
preferenceManager.set('enable-clipboard-notice', false)
|
||||||
preferenceManager.set('receive-encoding', 'raw')
|
preferenceManager.set('receive-encoding', 'raw')
|
||||||
@@ -239,16 +282,56 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let css = require('../hterm.userCSS.scss')
|
||||||
|
if (!config.terminal.ligatures) {
|
||||||
|
css += `
|
||||||
|
* {
|
||||||
|
font-feature-settings: "liga" 0;
|
||||||
|
font-variant-ligatures: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
} else {
|
||||||
|
css += `
|
||||||
|
* {
|
||||||
|
font-feature-settings: "liga" 1;
|
||||||
|
font-variant-ligatures: initial;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
css += config.appearance.css
|
||||||
|
this.hterm.setCSS(css)
|
||||||
this.hterm.setBracketedPaste(config.terminal.bracketedPaste)
|
this.hterm.setBracketedPaste(config.terminal.bracketedPaste)
|
||||||
|
this.hterm.defaultCursorShape = {
|
||||||
|
block: hterm.hterm.Terminal.cursorShape.BLOCK,
|
||||||
|
underline: hterm.hterm.Terminal.cursorShape.UNDERLINE,
|
||||||
|
beam: hterm.hterm.Terminal.cursorShape.BEAM,
|
||||||
|
}[config.terminal.cursor]
|
||||||
|
this.hterm.applyCursorShape()
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomIn () {
|
||||||
|
this.zoom++
|
||||||
|
this.setFontSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut () {
|
||||||
|
this.zoom--
|
||||||
|
this.setFontSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
resetZoom () {
|
||||||
|
this.zoom = 0
|
||||||
|
this.setFontSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
this.decorators.forEach((decorator) => {
|
this.decorators.forEach(decorator => {
|
||||||
decorator.detach(this)
|
decorator.detach(this)
|
||||||
})
|
})
|
||||||
this.configSubscription.unsubscribe()
|
this.hotkeysSubscription.unsubscribe()
|
||||||
this.sessionCloseSubscription.unsubscribe()
|
if (this.sessionCloseSubscription) {
|
||||||
this.size$.complete()
|
this.sessionCloseSubscription.unsubscribe()
|
||||||
|
}
|
||||||
this.resize$.complete()
|
this.resize$.complete()
|
||||||
this.input$.complete()
|
this.input$.complete()
|
||||||
this.output$.complete()
|
this.output$.complete()
|
||||||
@@ -260,6 +343,23 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
|
|
||||||
async destroy () {
|
async destroy () {
|
||||||
super.destroy()
|
super.destroy()
|
||||||
await this.session.destroy()
|
if (this.session && this.session.open) {
|
||||||
|
await this.session.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async canClose (): Promise<boolean> {
|
||||||
|
if (this.hostApp.platform === Platform.Windows) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let children = await this.session.getChildProcesses()
|
||||||
|
if (children.length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return confirm(`"${children[0].command}" is still running. Close?`)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setFontSize () {
|
||||||
|
preferenceManager.set('font-size', this.config.store.terminal.fontSize * Math.pow(1.1, this.zoom))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,8 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
bell: 'off',
|
bell: 'off',
|
||||||
bracketedPaste: false,
|
bracketedPaste: false,
|
||||||
background: 'theme',
|
background: 'theme',
|
||||||
|
ligatures: false,
|
||||||
|
cursor: 'block',
|
||||||
colorScheme: {
|
colorScheme: {
|
||||||
__nonStructural: true,
|
__nonStructural: true,
|
||||||
name: 'Material',
|
name: 'Material',
|
||||||
@@ -40,9 +42,26 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
[Platform.macOS]: {
|
[Platform.macOS]: {
|
||||||
terminal: {
|
terminal: {
|
||||||
font: 'Menlo',
|
font: 'Menlo',
|
||||||
shell: '/bin/zsh',
|
shell: '~default-shell~',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'copy': [
|
||||||
|
'⌘-C',
|
||||||
|
],
|
||||||
|
'clear': [
|
||||||
|
'⌘-K',
|
||||||
|
],
|
||||||
|
'zoom-in': [
|
||||||
|
'⌘-=',
|
||||||
|
'⌘-Shift-+',
|
||||||
|
],
|
||||||
|
'zoom-out': [
|
||||||
|
'⌘--',
|
||||||
|
'⌘-Shift-_',
|
||||||
|
],
|
||||||
|
'reset-zoom': [
|
||||||
|
'⌘-0',
|
||||||
|
],
|
||||||
'new-tab': [
|
'new-tab': [
|
||||||
['Ctrl-A', 'C'],
|
['Ctrl-A', 'C'],
|
||||||
['Ctrl-A', 'Ctrl-C'],
|
['Ctrl-A', 'Ctrl-C'],
|
||||||
@@ -57,6 +76,23 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
shell: '~clink~',
|
shell: '~clink~',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'copy': [
|
||||||
|
'Ctrl-Shift-C',
|
||||||
|
],
|
||||||
|
'clear': [
|
||||||
|
'Ctrl-L',
|
||||||
|
],
|
||||||
|
'zoom-in': [
|
||||||
|
'Ctrl-=',
|
||||||
|
'Ctrl-Shift-+',
|
||||||
|
],
|
||||||
|
'zoom-out': [
|
||||||
|
'Ctrl--',
|
||||||
|
'Ctrl-Shift-_',
|
||||||
|
],
|
||||||
|
'reset-zoom': [
|
||||||
|
'Ctrl-0',
|
||||||
|
],
|
||||||
'new-tab': [
|
'new-tab': [
|
||||||
['Ctrl-A', 'C'],
|
['Ctrl-A', 'C'],
|
||||||
['Ctrl-A', 'Ctrl-C'],
|
['Ctrl-A', 'Ctrl-C'],
|
||||||
@@ -67,9 +103,26 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
[Platform.Linux]: {
|
[Platform.Linux]: {
|
||||||
terminal: {
|
terminal: {
|
||||||
font: 'Liberation Mono',
|
font: 'Liberation Mono',
|
||||||
shell: '/bin/bash',
|
shell: '~default-shell~',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'copy': [
|
||||||
|
'Ctrl-Shift-C',
|
||||||
|
],
|
||||||
|
'clear': [
|
||||||
|
'Ctrl-L',
|
||||||
|
],
|
||||||
|
'zoom-in': [
|
||||||
|
'Ctrl-=',
|
||||||
|
'Ctrl-Shift-+',
|
||||||
|
],
|
||||||
|
'zoom-out': [
|
||||||
|
'Ctrl--',
|
||||||
|
'Ctrl-Shift-_',
|
||||||
|
],
|
||||||
|
'reset-zoom': [
|
||||||
|
'Ctrl-0',
|
||||||
|
],
|
||||||
'new-tab': [
|
'new-tab': [
|
||||||
['Ctrl-A', 'C'],
|
['Ctrl-A', 'C'],
|
||||||
['Ctrl-A', 'Ctrl-C'],
|
['Ctrl-A', 'Ctrl-C'],
|
||||||
|
BIN
terminus-terminal/src/fonts/Meslo.otf
Normal file
BIN
terminus-terminal/src/fonts/Meslo.otf
Normal file
Binary file not shown.
32
terminus-terminal/src/hotkeys.ts
Normal file
32
terminus-terminal/src/hotkeys.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { IHotkeyDescription, HotkeyProvider } from 'terminus-core'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TerminalHotkeyProvider extends HotkeyProvider {
|
||||||
|
hotkeys: IHotkeyDescription[] = [
|
||||||
|
{
|
||||||
|
id: 'copy',
|
||||||
|
name: 'Copy to clipboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'clear',
|
||||||
|
name: 'Clear terminal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zoom-in',
|
||||||
|
name: 'Zoom in',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zoom-out',
|
||||||
|
name: 'Zoom out',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'reset-zoom',
|
||||||
|
name: 'Reset zoom',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'new-tab',
|
||||||
|
name: 'New tab',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
@@ -1,4 +1,3 @@
|
|||||||
const dataurl = require('dataurl')
|
|
||||||
export const hterm = require('hterm-umdjs')
|
export const hterm = require('hterm-umdjs')
|
||||||
hterm.hterm.defaultStorage = new hterm.lib.Storage.Memory()
|
hterm.hterm.defaultStorage = new hterm.lib.Storage.Memory()
|
||||||
export const preferenceManager = new hterm.hterm.PreferenceManager('default')
|
export const preferenceManager = new hterm.hterm.PreferenceManager('default')
|
||||||
@@ -16,14 +15,61 @@ hterm.hterm.VT.ESC['k'] = function (parseState) {
|
|||||||
parseState.func = parseOSC
|
parseState.func = parseOSC
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceManager.set('user-css', dataurl.convert({
|
|
||||||
data: require('./hterm.userCSS.scss'),
|
|
||||||
mimetype: 'text/css',
|
|
||||||
charset: 'utf8',
|
|
||||||
}))
|
|
||||||
preferenceManager.set('background-color', '#1D272D')
|
preferenceManager.set('background-color', '#1D272D')
|
||||||
preferenceManager.set('color-palette-overrides', {
|
preferenceManager.set('color-palette-overrides', {
|
||||||
0: '#1D272D',
|
0: '#1D272D',
|
||||||
})
|
})
|
||||||
|
|
||||||
hterm.hterm.Terminal.prototype.showOverlay = () => null
|
hterm.hterm.Terminal.prototype.showOverlay = () => null
|
||||||
|
|
||||||
|
hterm.hterm.Terminal.prototype.setCSS = function (css) {
|
||||||
|
const doc = this.scrollPort_.document_
|
||||||
|
if (!doc.querySelector('#user-css')) {
|
||||||
|
const node = doc.createElement('style')
|
||||||
|
node.id = 'user-css'
|
||||||
|
doc.head.appendChild(node)
|
||||||
|
}
|
||||||
|
doc.querySelector('#user-css').innerText = css
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldCharWidthDisregardAmbiguous = hterm.lib.wc.charWidthDisregardAmbiguous
|
||||||
|
hterm.lib.wc.charWidthDisregardAmbiguous = codepoint => {
|
||||||
|
if ((codepoint >= 0x1f300 && codepoint <= 0x1f64f) ||
|
||||||
|
(codepoint >= 0x1f680 && codepoint <= 0x1f6ff)) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return oldCharWidthDisregardAmbiguous(codepoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
hterm.hterm.Terminal.prototype.applyCursorShape = function () {
|
||||||
|
let modes = [
|
||||||
|
[hterm.hterm.Terminal.cursorShape.BLOCK, true],
|
||||||
|
[this.defaultCursorShape || hterm.hterm.Terminal.cursorShape.BLOCK, false],
|
||||||
|
[hterm.hterm.Terminal.cursorShape.BLOCK, false],
|
||||||
|
[hterm.hterm.Terminal.cursorShape.UNDERLINE, true],
|
||||||
|
[hterm.hterm.Terminal.cursorShape.UNDERLINE, false],
|
||||||
|
[hterm.hterm.Terminal.cursorShape.BEAM, true],
|
||||||
|
[hterm.hterm.Terminal.cursorShape.BEAM, false],
|
||||||
|
]
|
||||||
|
let modeNumber = this.cursorMode || 1
|
||||||
|
console.log('mode', modeNumber)
|
||||||
|
if (modeNumber >= modes.length) {
|
||||||
|
console.warn('Unknown cursor style: ' + modeNumber)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.setCursorShape(modes[modeNumber][0])
|
||||||
|
this.setCursorBlink(modes[modeNumber][1])
|
||||||
|
}
|
||||||
|
|
||||||
|
hterm.hterm.VT.CSI[' q'] = function (parseState) {
|
||||||
|
const arg = parseState.args[0]
|
||||||
|
this.terminal.cursorMode = arg
|
||||||
|
this.terminal.applyCursorShape()
|
||||||
|
}
|
||||||
|
|
||||||
|
const _collapseToEnd = Selection.prototype.collapseToEnd
|
||||||
|
Selection.prototype.collapseToEnd = function () {
|
||||||
|
try {
|
||||||
|
_collapseToEnd.apply(this)
|
||||||
|
} catch (err) { ; }
|
||||||
|
}
|
||||||
|
@@ -6,11 +6,11 @@ a:hover {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
|
||||||
font-feature-settings: "liga" 0; // disable ligatures (they break monospacing)
|
|
||||||
}
|
|
||||||
|
|
||||||
x-screen {
|
x-screen {
|
||||||
transition: 0.125s ease background;
|
transition: 0.125s ease background;
|
||||||
padding-right: 15px;
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "monospace-fallback";
|
||||||
|
src: url(fonts/Meslo.otf) format("opentype");
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser'
|
|||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
import { HostAppService, Platform, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService } from 'terminus-core'
|
import { HostAppService, Platform, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider } from 'terminus-core'
|
||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
@@ -11,14 +11,17 @@ import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.c
|
|||||||
import { ColorPickerComponent } from './components/colorPicker.component'
|
import { ColorPickerComponent } from './components/colorPicker.component'
|
||||||
|
|
||||||
import { SessionsService } from './services/sessions.service'
|
import { SessionsService } from './services/sessions.service'
|
||||||
|
import { ShellsService } from './services/shells.service'
|
||||||
|
|
||||||
import { ScreenPersistenceProvider } from './persistenceProviders'
|
import { ScreenPersistenceProvider } from './persistenceProviders'
|
||||||
|
import { TMuxPersistenceProvider } from './tmux'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { RecoveryProvider } from './recoveryProvider'
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator } from './api'
|
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator } from './api'
|
||||||
import { TerminalSettingsTabProvider } from './settings'
|
import { TerminalSettingsTabProvider } from './settings'
|
||||||
import { PathDropDecorator } from './pathDrop'
|
import { PathDropDecorator } from './pathDrop'
|
||||||
import { TerminalConfigProvider } from './config'
|
import { TerminalConfigProvider } from './config'
|
||||||
|
import { TerminalHotkeyProvider } from './hotkeys'
|
||||||
import { HyperColorSchemes } from './colorSchemes'
|
import { HyperColorSchemes } from './colorSchemes'
|
||||||
import { hterm } from './hterm'
|
import { hterm } from './hterm'
|
||||||
|
|
||||||
@@ -30,22 +33,34 @@ import { hterm } from './hterm'
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SessionsService,
|
SessionsService,
|
||||||
|
ShellsService,
|
||||||
ScreenPersistenceProvider,
|
ScreenPersistenceProvider,
|
||||||
|
TMuxPersistenceProvider,
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
{
|
{
|
||||||
provide: SessionPersistenceProvider,
|
provide: SessionPersistenceProvider,
|
||||||
useFactory: (hostApp: HostAppService, screen: ScreenPersistenceProvider) => {
|
useFactory: (
|
||||||
|
hostApp: HostAppService,
|
||||||
|
screen: ScreenPersistenceProvider,
|
||||||
|
tmux: TMuxPersistenceProvider,
|
||||||
|
) => {
|
||||||
if (hostApp.platform === Platform.Windows) {
|
if (hostApp.platform === Platform.Windows) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
return screen
|
if (tmux.isAvailable()) {
|
||||||
|
tmux.init()
|
||||||
|
return tmux
|
||||||
|
} else {
|
||||||
|
return screen
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deps: [HostAppService, ScreenPersistenceProvider],
|
deps: [HostAppService, ScreenPersistenceProvider, TMuxPersistenceProvider],
|
||||||
},
|
},
|
||||||
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
|
||||||
|
{ provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
|
||||||
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
|
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
|
||||||
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
|
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
|
||||||
],
|
],
|
||||||
|
@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core'
|
|||||||
import { TerminalDecorator } from './api'
|
import { TerminalDecorator } from './api'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PathDropDecorator extends TerminalDecorator {
|
export class PathDropDecorator extends TerminalDecorator {
|
||||||
attach (terminal: TerminalTabComponent): void {
|
attach (terminal: TerminalTabComponent): void {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import * as fs from 'fs-promise'
|
import * as fs from 'mz/fs'
|
||||||
import { exec, spawn } from 'mz/child_process'
|
import { exec, spawn } from 'mz/child_process'
|
||||||
import { exec as execCallback } from 'child_process'
|
import { exec as execCallback } from 'child_process'
|
||||||
|
|
||||||
@@ -64,12 +64,13 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
recoveryId,
|
recoveryId,
|
||||||
recoveredTruePID$: truePID$.asObservable(),
|
recoveredTruePID$: truePID$.asObservable(),
|
||||||
command: 'screen',
|
command: 'screen',
|
||||||
args: ['-r', recoveryId],
|
args: ['-d', '-r', recoveryId, '-c', await this.prepareConfig()],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async extractShellPID (screenPID: number): Promise<number> {
|
async extractShellPID (screenPID: number): Promise<number> {
|
||||||
let child = (await listProcesses()).find(x => x.ppid === screenPID)
|
let processes = await listProcesses()
|
||||||
|
let child = processes.find(x => x.ppid === screenPID)
|
||||||
|
|
||||||
if (!child) {
|
if (!child) {
|
||||||
throw new Error(`Could not find any children of the screen process (PID ${screenPID})!`)
|
throw new Error(`Could not find any children of the screen process (PID ${screenPID})!`)
|
||||||
@@ -77,30 +78,15 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
|
|
||||||
if (child.command === 'login') {
|
if (child.command === 'login') {
|
||||||
await delay(1000)
|
await delay(1000)
|
||||||
child = (await listProcesses()).find(x => x.ppid === child.pid)
|
child = processes.find(x => x.ppid === child.pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
return child.pid
|
return child.pid
|
||||||
}
|
}
|
||||||
|
|
||||||
async startSession (options: SessionOptions): Promise<any> {
|
async startSession (options: SessionOptions): Promise<any> {
|
||||||
let configPath = '/tmp/.termScreenConfig'
|
|
||||||
await fs.writeFile(configPath, `
|
|
||||||
escape ^^^
|
|
||||||
vbell on
|
|
||||||
deflogin off
|
|
||||||
term xterm-color
|
|
||||||
bindkey "^[OH" beginning-of-line
|
|
||||||
bindkey "^[OF" end-of-line
|
|
||||||
bindkey "\\027[?1049h" stuff ----alternate enter-----
|
|
||||||
bindkey "\\027[?1049l" stuff ----alternate leave-----
|
|
||||||
termcapinfo xterm* 'hs:ts=\\E]0;:fs=\\007:ds=\\E]0;\\007'
|
|
||||||
defhstatus "^Et"
|
|
||||||
hardstatus off
|
|
||||||
altscreen on
|
|
||||||
`, 'utf-8')
|
|
||||||
let recoveryId = `term-tab-${Date.now()}`
|
let recoveryId = `term-tab-${Date.now()}`
|
||||||
let args = ['-d', '-m', '-c', configPath, '-U', '-S', recoveryId, '-T', 'xterm-256color', '--', options.command].concat(options.args || [])
|
let args = ['-d', '-m', '-c', await this.prepareConfig(), '-U', '-S', recoveryId, '-T', 'xterm-256color', '--', '-' + options.command].concat(options.args || [])
|
||||||
this.logger.debug('Spawning screen with', args.join(' '))
|
this.logger.debug('Spawning screen with', args.join(' '))
|
||||||
await spawn('screen', args, {
|
await spawn('screen', args, {
|
||||||
cwd: options.cwd,
|
cwd: options.cwd,
|
||||||
@@ -110,6 +96,34 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async terminateSession (recoveryId: string): Promise<void> {
|
async terminateSession (recoveryId: string): Promise<void> {
|
||||||
await exec(`screen -S ${recoveryId} -X quit`)
|
try {
|
||||||
|
await exec(`screen -S ${recoveryId} -X quit`)
|
||||||
|
} catch (_) {
|
||||||
|
// screen has already quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async prepareConfig (): Promise<string> {
|
||||||
|
let configPath = '/tmp/.termScreenConfig'
|
||||||
|
await fs.writeFile(configPath, `
|
||||||
|
escape ^^^
|
||||||
|
vbell off
|
||||||
|
deflogin on
|
||||||
|
defflow off
|
||||||
|
term xterm-color
|
||||||
|
bindkey "^[OH" beginning-of-line
|
||||||
|
bindkey "^[OF" end-of-line
|
||||||
|
bindkey "^[[H" beginning-of-line
|
||||||
|
bindkey "^[[F" end-of-line
|
||||||
|
bindkey "\\027[?1049h" stuff ----alternate enter-----
|
||||||
|
bindkey "\\027[?1049l" stuff ----alternate leave-----
|
||||||
|
termcapinfo xterm* 'hs:ts=\\E]0;:fs=\\007:ds=\\E]0;\\007'
|
||||||
|
defhstatus "^Et"
|
||||||
|
hardstatus off
|
||||||
|
altscreen on
|
||||||
|
defutf8 on
|
||||||
|
defencoding utf8
|
||||||
|
`, 'utf-8')
|
||||||
|
return configPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TabRecoveryProvider, AppService } from 'terminus-core'
|
import { TabRecoveryProvider, RecoveredTab } from 'terminus-core'
|
||||||
|
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
import { SessionsService } from './services/sessions.service'
|
import { SessionsService } from './services/sessions.service'
|
||||||
@@ -8,18 +8,21 @@ import { SessionsService } from './services/sessions.service'
|
|||||||
export class RecoveryProvider extends TabRecoveryProvider {
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
constructor (
|
constructor (
|
||||||
private sessions: SessionsService,
|
private sessions: SessionsService,
|
||||||
private app: AppService,
|
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
async recover (recoveryToken: any): Promise<void> {
|
async recover (recoveryToken: any): Promise<RecoveredTab> {
|
||||||
if (recoveryToken.type === 'app:terminal') {
|
if (recoveryToken.type === 'app:terminal') {
|
||||||
let sessionOptions = await this.sessions.recover(recoveryToken.recoveryId)
|
let sessionOptions = await this.sessions.recover(recoveryToken.recoveryId)
|
||||||
if (!sessionOptions) {
|
if (!sessionOptions) {
|
||||||
return
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: TerminalTabComponent,
|
||||||
|
options: { sessionOptions },
|
||||||
}
|
}
|
||||||
this.app.openNewTab(TerminalTabComponent, { sessionOptions })
|
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,20 @@
|
|||||||
import * as nodePTY from 'node-pty'
|
const psNode = require('ps-node')
|
||||||
import * as fs from 'fs-promise'
|
// import * as nodePTY from 'node-pty'
|
||||||
|
let nodePTY
|
||||||
|
import * as fs from 'mz/fs'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Logger, LogService } from 'terminus-core'
|
import { Logger, LogService, ElectronService } from 'terminus-core'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
|
|
||||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
|
export interface IChildProcess {
|
||||||
|
pid: number
|
||||||
|
ppid: number
|
||||||
|
command: string
|
||||||
|
}
|
||||||
|
|
||||||
export class Session {
|
export class Session {
|
||||||
open: boolean
|
open: boolean
|
||||||
name: string
|
name: string
|
||||||
@@ -25,12 +33,20 @@ export class Session {
|
|||||||
|
|
||||||
let env = {
|
let env = {
|
||||||
...process.env,
|
...process.env,
|
||||||
...options.env,
|
|
||||||
TERM: 'xterm-256color',
|
TERM: 'xterm-256color',
|
||||||
|
...options.env,
|
||||||
}
|
}
|
||||||
if (options.command.includes(' ')) {
|
|
||||||
options.args = ['-c', options.command]
|
if (process.platform === 'darwin' && !process.env.LC_ALL) {
|
||||||
options.command = 'sh'
|
let locale = process.env.LC_CTYPE || 'en_US.UTF-8'
|
||||||
|
Object.assign(env, {
|
||||||
|
LANG: locale,
|
||||||
|
LC_ALL: locale,
|
||||||
|
LC_MESSAGES: locale,
|
||||||
|
LC_NUMERIC: locale,
|
||||||
|
LC_COLLATE: locale,
|
||||||
|
LC_MONETARY: locale,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
this.pty = nodePTY.spawn(options.command, options.args || [], {
|
this.pty = nodePTY.spawn(options.command, options.args || [], {
|
||||||
name: 'xterm-256color',
|
name: 'xterm-256color',
|
||||||
@@ -58,6 +74,12 @@ export class Session {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.pty.on('exit', () => {
|
||||||
|
if (this.open) {
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.pty.on('close', () => {
|
this.pty.on('close', () => {
|
||||||
if (this.open) {
|
if (this.open) {
|
||||||
this.destroy()
|
this.destroy()
|
||||||
@@ -72,17 +94,35 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resize (columns, rows) {
|
resize (columns, rows) {
|
||||||
this.pty.resize(columns, rows)
|
if (this.pty.writable) {
|
||||||
|
this.pty.resize(columns, rows)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write (data) {
|
write (data) {
|
||||||
this.pty.write(data)
|
if (this.pty.writable) {
|
||||||
|
this.pty.write(Buffer.from(data, 'utf-8'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kill (signal?: string) {
|
kill (signal?: string) {
|
||||||
this.pty.kill(signal)
|
this.pty.kill(signal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getChildProcesses (): Promise<IChildProcess[]> {
|
||||||
|
if (!this.truePID) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return new Promise<IChildProcess[]>((resolve, reject) => {
|
||||||
|
psNode.lookup({ ppid: this.truePID }, (err, processes) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
resolve(processes as IChildProcess[])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async gracefullyKillProcess (): Promise<void> {
|
async gracefullyKillProcess (): Promise<void> {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
this.kill()
|
this.kill()
|
||||||
@@ -118,7 +158,11 @@ export class Session {
|
|||||||
async getWorkingDirectory (): Promise<string> {
|
async getWorkingDirectory (): Promise<string> {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
let lines = (await exec(`lsof -p ${this.truePID} -Fn`))[0].toString().split('\n')
|
let lines = (await exec(`lsof -p ${this.truePID} -Fn`))[0].toString().split('\n')
|
||||||
return lines[2].substring(1)
|
if (lines[1] === 'fcwd') {
|
||||||
|
return lines[2].substring(1)
|
||||||
|
} else {
|
||||||
|
return lines[1].substring(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
return await fs.readlink(`/proc/${this.truePID}/cwd`)
|
return await fs.readlink(`/proc/${this.truePID}/cwd`)
|
||||||
@@ -135,8 +179,10 @@ export class SessionsService {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private persistence: SessionPersistenceProvider,
|
private persistence: SessionPersistenceProvider,
|
||||||
|
electron: ElectronService,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
|
nodePTY = electron.remoteRequirePluginModule('terminus-terminal', 'node-pty', global as any)
|
||||||
this.logger = log.create('sessions')
|
this.logger = log.create('sessions')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
58
terminus-terminal/src/services/shells.service.ts
Normal file
58
terminus-terminal/src/services/shells.service.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import { exec } from 'mz/child_process'
|
||||||
|
import * as fs from 'mz/fs'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { ElectronService, HostAppService, Platform, Logger, LogService } from 'terminus-core'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ShellsService {
|
||||||
|
private logger: Logger
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
log: LogService,
|
||||||
|
private electron: ElectronService,
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
this.logger = log.create('shells')
|
||||||
|
}
|
||||||
|
|
||||||
|
getClinkOptions (): { command, args } {
|
||||||
|
return {
|
||||||
|
command: 'cmd.exe',
|
||||||
|
args: [
|
||||||
|
'/k',
|
||||||
|
path.join(
|
||||||
|
path.dirname(this.electron.app.getPath('exe')),
|
||||||
|
'resources',
|
||||||
|
'clink',
|
||||||
|
`clink_${process.arch}.exe`,
|
||||||
|
),
|
||||||
|
'inject',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDefaultShell (): Promise<string> {
|
||||||
|
if (this.hostApp.platform === Platform.macOS) {
|
||||||
|
return this.getDefaultMacOSShell()
|
||||||
|
} else {
|
||||||
|
return this.getDefaultLinuxShell()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDefaultMacOSShell (): Promise<string> {
|
||||||
|
let shellEntry = (await exec(`dscl . -read /Users/${process.env.LOGNAME} UserShell`))[0].toString()
|
||||||
|
return shellEntry.split(' ')[1].trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDefaultLinuxShell (): Promise<string> {
|
||||||
|
let line = (await fs.readFile('/etc/passwd', { encoding: 'utf-8' }))
|
||||||
|
.split('\n').find(x => x.startsWith(process.env.LOGNAME + ':'))
|
||||||
|
if (!line) {
|
||||||
|
this.logger.warn('Could not detect user shell')
|
||||||
|
return '/bin/sh'
|
||||||
|
} else {
|
||||||
|
return line.split(':')[6]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
207
terminus-terminal/src/tmux.ts
Normal file
207
terminus-terminal/src/tmux.ts
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { execFileSync } from 'child_process'
|
||||||
|
import * as AsyncLock from 'async-lock'
|
||||||
|
import { ConnectableObservable, Subject } from 'rxjs'
|
||||||
|
import * as childProcess from 'child_process'
|
||||||
|
import { SessionOptions, SessionPersistenceProvider } from './api'
|
||||||
|
|
||||||
|
const TMUX_CONFIG = `
|
||||||
|
set -g status off
|
||||||
|
set -g focus-events on
|
||||||
|
set -g bell-action any
|
||||||
|
set -g bell-on-alert on
|
||||||
|
set -g visual-bell off
|
||||||
|
set -g set-titles on
|
||||||
|
set -g set-titles-string "#W"
|
||||||
|
set -g window-status-format '#I:#(pwd="#{pane_current_path}"; echo \${pwd####*/})#F'
|
||||||
|
set -g window-status-current-format '#I:#(pwd="#{pane_current_path}"; echo \${pwd####*/})#F'
|
||||||
|
set-option -g status-interval 1
|
||||||
|
`
|
||||||
|
|
||||||
|
export class TMuxBlock {
|
||||||
|
time: number
|
||||||
|
number: number
|
||||||
|
error: boolean
|
||||||
|
lines: string[]
|
||||||
|
|
||||||
|
constructor (line: string) {
|
||||||
|
this.time = parseInt(line.split(' ')[1])
|
||||||
|
this.number = parseInt(line.split(' ')[2])
|
||||||
|
this.lines = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TMuxMessage {
|
||||||
|
type: string
|
||||||
|
content: string
|
||||||
|
|
||||||
|
constructor (line: string) {
|
||||||
|
this.type = line.substring(0, line.indexOf(' '))
|
||||||
|
this.content = line.substring(line.indexOf(' ') + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TMuxCommandProcess {
|
||||||
|
private process: childProcess.ChildProcess
|
||||||
|
private rawOutput$ = new Subject<string>()
|
||||||
|
private line$ = new Subject<string>()
|
||||||
|
private message$ = new Subject<string>()
|
||||||
|
private block$ = new Subject<TMuxBlock>()
|
||||||
|
private response$: ConnectableObservable<TMuxBlock>
|
||||||
|
private lock = new AsyncLock({ timeout: 1000 })
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
this.process = childProcess.spawn('tmux', ['-C', '-f', '/dev/null', '-L', 'terminus', 'new-session', '-A', '-D', '-s', 'control'])
|
||||||
|
console.log('[tmux] started')
|
||||||
|
this.process.stdout.on('data', data => {
|
||||||
|
// console.debug('tmux says:', data.toString())
|
||||||
|
this.rawOutput$.next(data.toString())
|
||||||
|
})
|
||||||
|
|
||||||
|
let rawBuffer = ''
|
||||||
|
this.rawOutput$.subscribe(raw => {
|
||||||
|
rawBuffer += raw
|
||||||
|
if (rawBuffer.includes('\n')) {
|
||||||
|
let lines = rawBuffer.split('\n')
|
||||||
|
rawBuffer = lines.pop()
|
||||||
|
lines.forEach(line => this.line$.next(line))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let currentBlock = null
|
||||||
|
this.line$.subscribe(line => {
|
||||||
|
if (currentBlock) {
|
||||||
|
if (line.startsWith('%end ')) {
|
||||||
|
let block = currentBlock
|
||||||
|
currentBlock = null
|
||||||
|
setImmediate(() => {
|
||||||
|
this.block$.next(block)
|
||||||
|
})
|
||||||
|
} else if (line.startsWith('%error ')) {
|
||||||
|
let block = currentBlock
|
||||||
|
block.error = true
|
||||||
|
currentBlock = null
|
||||||
|
setImmediate(() => {
|
||||||
|
this.block$.next(block)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
currentBlock.lines.push(line)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (line.startsWith('%begin ')) {
|
||||||
|
currentBlock = new TMuxBlock(line)
|
||||||
|
} else {
|
||||||
|
this.message$.next(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.response$ = this.block$.skip(1).publish()
|
||||||
|
this.response$.connect()
|
||||||
|
|
||||||
|
this.block$.subscribe(block => {
|
||||||
|
console.debug('[tmux] block:', block)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.message$.subscribe(message => {
|
||||||
|
console.debug('[tmux] message:', message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
command (command: string): Promise<TMuxBlock> {
|
||||||
|
return this.lock.acquire('key', () => {
|
||||||
|
let p = this.response$.take(1).toPromise()
|
||||||
|
console.debug('[tmux] command:', command)
|
||||||
|
this.process.stdin.write(command + '\n')
|
||||||
|
return p
|
||||||
|
}).then(response => {
|
||||||
|
if (response.error) {
|
||||||
|
throw response
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}) as Promise<TMuxBlock>
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
this.rawOutput$.complete()
|
||||||
|
this.line$.complete()
|
||||||
|
this.block$.complete()
|
||||||
|
this.message$.complete()
|
||||||
|
this.process.kill('SIGTERM')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TMux {
|
||||||
|
private process: TMuxCommandProcess
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
this.process = new TMuxCommandProcess()
|
||||||
|
TMUX_CONFIG.split('\n').filter(x => x).forEach(async (line) => {
|
||||||
|
await this.process.command(line)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async create (id: string, options: SessionOptions): Promise<void> {
|
||||||
|
let args = [options.command].concat(options.args)
|
||||||
|
let cmd = args.map(x => `"${x.replace('"', '\\"')}"`)
|
||||||
|
await this.process.command(
|
||||||
|
`new-session -s "${id}" -d`
|
||||||
|
+ (options.cwd ? ` -c '${options.cwd.replace("'", "\\'")}'` : '')
|
||||||
|
+ ` '${cmd}'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async list (): Promise<string[]> {
|
||||||
|
let block = await this.process.command('list-sessions -F "#{session_name}"')
|
||||||
|
return block.lines
|
||||||
|
}
|
||||||
|
|
||||||
|
async terminate (id: string): Promise<void> {
|
||||||
|
await this.process.command(`kill-session -t ${id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TMuxPersistenceProvider extends SessionPersistenceProvider {
|
||||||
|
private tmux: TMux
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
isAvailable (): boolean {
|
||||||
|
try {
|
||||||
|
execFileSync('tmux', ['-V'])
|
||||||
|
return true
|
||||||
|
} catch (_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init () {
|
||||||
|
this.tmux = new TMux()
|
||||||
|
}
|
||||||
|
|
||||||
|
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||||
|
let sessions = await this.tmux.list()
|
||||||
|
if (!sessions.includes(recoveryId)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
command: 'tmux',
|
||||||
|
args: ['-L', 'terminus', 'attach-session', '-d', '-t', recoveryId],
|
||||||
|
recoveryId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startSession (options: SessionOptions): Promise<any> {
|
||||||
|
// TODO env
|
||||||
|
let recoveryId = Date.now().toString()
|
||||||
|
await this.tmux.create(recoveryId, options)
|
||||||
|
return recoveryId
|
||||||
|
}
|
||||||
|
|
||||||
|
async terminateSession (recoveryId: string): Promise<void> {
|
||||||
|
await this.tmux.terminate(recoveryId)
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,10 @@
|
|||||||
"exclude": ["node_modules", "dist"],
|
"exclude": ["node_modules", "dist"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"declarationDir": "dist"
|
"declarationDir": "dist",
|
||||||
|
"paths": {
|
||||||
|
"terminus-*": ["terminus-*"],
|
||||||
|
"*": ["app/node_modules/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,14 +34,22 @@ module.exports = {
|
|||||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||||
{ test: /\.css$/, use: ['to-string-loader', 'css-loader'] },
|
{ test: /\.css$/, use: ['to-string-loader', 'css-loader'] },
|
||||||
|
{
|
||||||
|
test: /\.(ttf|eot|otf|woff|woff2|ogg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
|
loader: "url-loader",
|
||||||
|
options: {
|
||||||
|
limit: 999999999999,
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
|
'electron',
|
||||||
'fs',
|
'fs',
|
||||||
'fs-promise',
|
|
||||||
'font-manager',
|
'font-manager',
|
||||||
'path',
|
'path',
|
||||||
'node-pty',
|
'node-pty',
|
||||||
|
'mz/fs',
|
||||||
'mz/child_process',
|
'mz/child_process',
|
||||||
'winreg',
|
'winreg',
|
||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
|
135
terminus-terminal/yarn.lock
Normal file
135
terminus-terminal/yarn.lock
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@types/deep-equal@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.0.tgz#9ebeaa73d1fc4791f038a5f1440e0449ea968495"
|
||||||
|
|
||||||
|
"@types/mz@0.0.31":
|
||||||
|
version "0.0.31"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.31.tgz#a4d80c082fefe71e40a7c0f07d1e6555bbbc7b52"
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/node@*", "@types/node@7.0.12":
|
||||||
|
version "7.0.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.12.tgz#ae5f67a19c15f752148004db07cbbb372e69efc9"
|
||||||
|
|
||||||
|
"@types/webpack-env@1.13.0":
|
||||||
|
version "1.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.0.tgz#3044381647e11ee973c5af2e925323930f691d80"
|
||||||
|
|
||||||
|
"@types/winreg@^1.2.30":
|
||||||
|
version "1.2.30"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/winreg/-/winreg-1.2.30.tgz#91d6710e536d345b9c9b017c574cf6a8da64c518"
|
||||||
|
|
||||||
|
any-promise@^1.0.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||||
|
|
||||||
|
big.js@^3.1.3:
|
||||||
|
version "3.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978"
|
||||||
|
|
||||||
|
connected-domain@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93"
|
||||||
|
|
||||||
|
dataurl@0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dataurl/-/dataurl-0.1.0.tgz#1f4734feddec05ffe445747978d86759c4b33199"
|
||||||
|
|
||||||
|
deep-equal@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||||
|
|
||||||
|
emojis-list@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
|
||||||
|
|
||||||
|
file-loader@^0.11.2:
|
||||||
|
version "0.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.11.2.tgz#4ff1df28af38719a6098093b88c82c71d1794a34"
|
||||||
|
dependencies:
|
||||||
|
loader-utils "^1.0.2"
|
||||||
|
|
||||||
|
font-manager@0.2.2:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/font-manager/-/font-manager-0.2.2.tgz#18a1c5b6ec7f91e22a17c71cbbaa0ea4e68e3a44"
|
||||||
|
dependencies:
|
||||||
|
nan "~2.2.0"
|
||||||
|
|
||||||
|
hterm-umdjs@1.1.3:
|
||||||
|
version "1.1.3+1.58.sha.15ed490"
|
||||||
|
resolved "https://registry.yarnpkg.com/hterm-umdjs/-/hterm-umdjs-1.1.3.tgz#8b57bcaded5ba9541d6c8e32a82b34abb93e885e"
|
||||||
|
|
||||||
|
json5@^0.5.0:
|
||||||
|
version "0.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
|
||||||
|
|
||||||
|
loader-utils@^1.0.2:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
|
||||||
|
dependencies:
|
||||||
|
big.js "^3.1.3"
|
||||||
|
emojis-list "^2.0.0"
|
||||||
|
json5 "^0.5.0"
|
||||||
|
|
||||||
|
mz@^2.6.0:
|
||||||
|
version "2.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mz/-/mz-2.6.0.tgz#c8b8521d958df0a4f2768025db69c719ee4ef1ce"
|
||||||
|
dependencies:
|
||||||
|
any-promise "^1.0.0"
|
||||||
|
object-assign "^4.0.1"
|
||||||
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
|
nan@2.5.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.0.tgz#aa8f1e34531d807e9e27755b234b4a6ec0c152a8"
|
||||||
|
|
||||||
|
nan@~2.2.0:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.2.1.tgz#d68693f6b34bb41d66bc68b3a4f9defc79d7149b"
|
||||||
|
|
||||||
|
node-pty@0.6.8:
|
||||||
|
version "0.6.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.6.8.tgz#a7b145397bef23a719128a75b20d4821726dfe90"
|
||||||
|
dependencies:
|
||||||
|
nan "2.5.0"
|
||||||
|
|
||||||
|
object-assign@^4.0.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
|
||||||
|
ps-node@^0.1.6:
|
||||||
|
version "0.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3"
|
||||||
|
dependencies:
|
||||||
|
table-parser "^0.1.3"
|
||||||
|
|
||||||
|
runes@^0.4.2:
|
||||||
|
version "0.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.2.tgz#1ddc1ea41de769cb32fc068a64fbbc45cd21052e"
|
||||||
|
|
||||||
|
table-parser@^0.1.3:
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0"
|
||||||
|
dependencies:
|
||||||
|
connected-domain "^1.0.0"
|
||||||
|
|
||||||
|
thenify-all@^1.0.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
||||||
|
dependencies:
|
||||||
|
thenify ">= 3.1.0 < 4"
|
||||||
|
|
||||||
|
"thenify@>= 3.1.0 < 4":
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
|
||||||
|
dependencies:
|
||||||
|
any-promise "^1.0.0"
|
||||||
|
|
||||||
|
winreg@^1.2.3:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b"
|
Reference in New Issue
Block a user