Compare commits

...

80 Commits

Author SHA1 Message Date
Eugene Pankov
1643ca2bd5 Merge branch 'master' of https://github.com/Eugeny/terminus 2017-07-10 18:26:47 +02:00
Eugene Pankov
b640abd85d set proper $TERM on Windows (fixes #91, fixes #28) 2017-07-10 18:26:46 +02:00
Tri Nguyen
dcf9817075 update Base16 Default Dark theme to 256 colors 2017-07-10 18:12:44 +02:00
Eugene Pankov
6f8f83d178 detect 32-bit Cygwin setups (fixes #106) 2017-07-10 18:11:47 +02:00
Eugene Pankov
475c4f91be handle directory paths supplied on cli (fixes #90) 2017-07-10 17:57:58 +02:00
Eugene Pankov
29a6fb60de make middle mouse button close tabs (fixes #92) 2017-07-10 17:35:01 +02:00
Eugene Pankov
42007f4fef upgraded node-pty (fixes #84) 2017-07-10 17:30:42 +02:00
Eugene Pankov
92c0df7629 Merge branch 'master' of github.com:Eugeny/terminus 2017-07-10 15:30:51 +02:00
Eugene Pankov
e70cb25180 non-debug start script 2017-07-10 15:25:50 +02:00
Eugene Pankov
980834df6f macOS UTF8 fixes fixed #31, fixed #40 2017-07-08 11:30:25 +02:00
Eugene Pankov
50968508df fixed cwd detection on el capitan (fixes #63) 2017-07-06 10:37:52 +02:00
Eugene Pankov
8ee93297be properly display Space in hotkeys (fixes #76) 2017-07-06 10:28:33 +02:00
Eugene Pankov
dc9b2553ae Merge branch 'master' of github.com:Eugeny/terminus 2017-07-06 10:28:31 +02:00
Eugene Pankov
a6f7f7aa05 re-re-fixed custom npm path detection 2017-07-05 21:13:41 +02:00
Eugene Pankov
c087a969d6 cmd-k to clear the terminal (fixes #39) 2017-07-05 19:31:58 +02:00
Eugene Pankov
f4eb03fee0 proper macOS menu (fixes #39) 2017-07-05 19:05:45 +02:00
Eugene Pankov
deb4b963cd copy hotkey for macOS (fixes #61) 2017-07-05 18:46:16 +02:00
Eugene Pankov
9834b27b8d bump 2017-07-05 16:24:03 +02:00
Eugene Pankov
fc060acd88 open new tabs from cli (fixes #67) 2017-07-05 16:22:44 +02:00
Eugene Pankov
709ffadc7c don't resize dead PTYs 2017-07-05 15:33:50 +02:00
Eugene Pankov
536d9537ff show plugin author names 2017-07-05 15:31:23 +02:00
Eugene Pankov
94217f0b01 ignore screen shutdown errors 2017-07-05 15:21:01 +02:00
Eugene Pankov
80762e92d6 don't crash when closing a background tab after recovery 2017-07-05 14:58:40 +02:00
Eugene Pankov
e6ef21fa9d replaced fs-promise with mz/fs 2017-07-05 14:48:02 +02:00
Eugene Pankov
da89560d6b fixed #2 - search for NPM in profile search paths 2017-07-05 14:46:04 +02:00
Eugene Pankov
c0c2373ed6 hopefully fixed #2 2017-07-05 12:49:01 +02:00
Eugene Pankov
f2a8eb92a1 add a custom npm path option (fixes #10) 2017-07-05 11:36:00 +02:00
Eugene Pankov
48e8ffd729 ligatures switch (fixes #51) 2017-07-05 11:26:28 +02:00
Eugene Pankov
7327a7008c . 2017-07-05 11:09:17 +02:00
Eugene Pankov
856c7e7e9e fixed #62 2017-07-05 11:07:46 +02:00
Eugene Pankov
353a4da083 added zoom hotkeys & mouse handler (fixes #24) 2017-07-05 11:01:03 +02:00
Eugene Pankov
3068c27fd6 Merge branch 'master' of github.com:Eugeny/terminus 2017-07-04 22:18:40 +02:00
Eugene Pankov
7de0bd95b9 fixed env.HOME on Windows (fixes #6, fixes #60) 2017-07-04 18:25:58 +02:00
Eugene Pankov
904828c3e3 hterm update 2017-07-04 18:17:23 +02:00
Eugene Pankov
63757f7726 bumped community color schemes 2017-07-04 16:15:53 +02:00
Tri Nguyen
05f16f1719 add Base16 color scheme
This was generated using https://github.com/mbadolato/iTerm2-Color-Schemes/blob/master/tools/xrdb2Xresources.py
from a xrdb file, which was generated by using https://github.com/mbadolato/iTerm2-Color-Schemes/blob/master/tools/iterm2xrdb
on https://github.com/chriskempson/base16-iterm2/blob/master/base16-default.dark.256.itermcolors
2017-07-04 16:15:17 +02:00
Tri Nguyen
78e115b698 npm is not Node Package Manager
npm does not (just?) stand for Node Package Manager
2017-07-04 11:51:21 +02:00
Idan Asraf
55b53ed5b7 Tab-close button outline removed 2017-07-04 11:50:42 +02:00
Eugene Pankov
1fa7b40913 Update HACKING.md 2017-07-04 11:48:03 +02:00
Eugene Pankov
5b7ded9097 make size non-observable 2017-07-03 19:00:58 +02:00
Eugene Pankov
06b60b86f2 emoji support (fixes #35) 2017-07-03 18:21:12 +02:00
Eugene Pankov
052c941275 yarn 2017-07-03 17:55:46 +02:00
Eugene Pankov
72899b0cf2 Merge branch 'master' of github.com:Eugeny/terminus 2017-07-03 17:50:36 +02:00
Eugene Pankov
342316f5a5 downgraded Electron (fixes #41) 2017-07-03 17:50:00 +02:00
Samuel Hilson
c215faaeb8 adding line to readme for copy and paste hotkey 2017-07-03 16:57:44 +02:00
Eugene Pankov
c4c342bd0a bumped color schemes 2017-07-03 15:25:13 +02:00
atelierbram
4472a033a1 added Base2Tone colorschemes 2017-07-03 15:23:26 +02:00
Eugene Pankov
093876a445 Delete .gitlab-ci.yml 2017-07-03 14:51:08 +02:00
Eugene Pankov
359e0926cb include version info in issues 2017-07-01 15:08:42 +02:00
Eugene Pankov
0e9282e079 docs 2017-07-01 14:13:07 +02:00
Eugene Pankov
5511f4d7cb docs fix 2017-07-01 14:03:14 +02:00
Eugene Pankov
3ae76e66ed Merge branch 'master' of github.com:Eugeny/terminus 2017-07-01 12:48:48 +02:00
Eugene Pankov
42059e3bc6 Create CODE_OF_CONDUCT.md 2017-07-01 10:54:04 +02:00
Eugene Pankov
c093b204d7 override plugin module resolution 2017-07-01 01:22:01 +02:00
Eugene Pankov
8a8f89b386 wip 2017-07-01 00:55:21 +02:00
Eugene Pankov
b109fb8766 plugin versions 2017-07-01 00:54:55 +02:00
Eugene Pankov
f58cf469ed Linux default shell fix 2017-06-30 21:20:03 +02:00
Eugene Pankov
57227ae6ce Merge branch 'master' of github.com:Eugeny/terminus 2017-06-30 21:08:19 +02:00
Eugene Pankov
b670fa843a don't force electron-debug 2017-06-30 21:08:12 +02:00
Eugene Pankov
c41e551c58 README update 2017-06-30 20:59:23 +02:00
Eugene Pankov
60b02b17e4 Start shell as login shell (fixes #2) 2017-06-30 20:52:22 +02:00
Eugene Pankov
bf8bb7ee80 use user's default shell (#2) 2017-06-30 20:44:17 +02:00
Eugene Pankov
1e1d48a5f8 rollback 2017-06-30 20:01:41 +02:00
Eugene Pankov
77b55a003c Git-bash support (fixes #12) 2017-06-30 19:54:17 +02:00
Eugene Pankov
165ab1cfbf version mgmt 2017-06-30 19:40:18 +02:00
Eugene Pankov
d4840bafaf Some docking fixes (fixed #13) 2017-06-30 19:39:01 +02:00
Eugene Pankov
9badd88c80 force GNU screen dependency on Linux (fixes #1) 2017-06-30 10:44:43 +02:00
Eugene Pankov
7a24c9aa3a don't auto enable devtools 2017-06-29 14:26:39 +02:00
Eugene Pankov
8a7e7c4eb5 fixes 2017-06-28 16:55:09 +02:00
Eugene Pankov
d0c10278fb build fixes 2017-06-27 23:35:19 +02:00
Eugene Pankov
2ae004db53 alpha 2017-06-27 23:19:18 +02:00
Eugene Pankov
7b34b668c1 readme 2017-06-27 23:13:05 +02:00
Eugene Pankov
7c09fc4a38 docs 2017-06-27 23:12:23 +02:00
Eugene Pankov
89ff9d0ea2 . 2017-06-26 22:01:08 +02:00
Eugene Pankov
b4e503b26c . 2017-06-26 21:54:36 +02:00
Eugene Pankov
edc911605b . 2017-06-24 20:15:09 +02:00
Eugene Pankov
30115d0996 . 2017-06-24 20:05:49 +02:00
Eugene Pankov
5204d944c6 . 2017-06-24 19:34:25 +02:00
Eugene Pankov
39335bc23a . 2017-06-24 19:24:49 +02:00
Eugene Pankov
cad417694d . 2017-06-24 19:13:44 +02:00
73 changed files with 6408 additions and 349 deletions

View File

@@ -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
View 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/

View File

@@ -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.

View File

@@ -1,2 +1,38 @@
[![Build Status](https://travis-ci.org/Eugeny/terminus.svg?branch=master)](https://travis-ci.org/Eugeny/terminus) # Terminus α
[![Build status](https://ci.appveyor.com/api/projects/status/wnnq4hm5mbd9rgoy?svg=true)](https://ci.appveyor.com/project/Eugeny/terminus) *A terminal for a more modern age*
[![Build Status](https://travis-ci.org/Eugeny/terminus.svg?branch=master)](https://travis-ci.org/Eugeny/terminus) [![Build status](https://ci.appveyor.com/api/projects/status/wnnq4hm5mbd9rgoy?svg=true)](https://ci.appveyor.com/project/Eugeny/terminus) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE) [![Downloads](https://img.shields.io/badge/downloads-latest_release-brightgreen.svg)](https://github.com/Eugeny/terminus/releases/latest)
----
![](https://github.com/Eugeny/terminus/raw/master/docs/linux.png)
**Terminus** is a web technology based terminal heavily inspired by Hyper. It is, however, designed for people who need to get things done.
* Runs on Windows, macOS and Linux
* Theming and color schemes
* Configurable hotkey schemes
* **GNU Screen** style hotkeys available by default
* Default Linux style hotkeys for Copy(`Ctrl`+`Shift`+`C`), and Paste(`Ctrl`+`Shift`+`V`)
* Full Unicode support including double-width characters
* Doesn't choke on fast-flowing outputs
* Tab persistence on macOS and Linux
* Proper shell-like experience on Windows including tab completion (thanks, Clink!)
* CMD, PowerShell, Cygwin, Git-Bash and Bash on Windows support
---
# 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
---
# Contributing
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 brief plugin development tutorial!

View File

@@ -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'})
@@ -107,25 +108,79 @@ setupWindowManagement = () => {
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.window.webContents.send('host:quit-request')
}
}
] ]
}, },
{ {
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') }
}
] ]
}] }]
@@ -146,7 +201,7 @@ start = () => {
let options = { let options = {
width: 800, width: 800,
height: 400, height: 600,
//icon: `${app.getAppPath()}/assets/img/icon.png`, //icon: `${app.getAppPath()}/assets/img/icon.png`,
title: 'Terminus', title: 'Terminus',
minWidth: 400, minWidth: 400,

View File

@@ -25,10 +25,13 @@
"electron-debug": "^1.0.1", "electron-debug": "^1.0.1",
"electron-is-dev": "0.1.2", "electron-is-dev": "0.1.2",
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"fs-promise": "^2.0.2",
"js-yaml": "3.8.2", "js-yaml": "3.8.2",
"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.4"
},
"devDependencies": {
"@types/mz": "0.0.31"
} }
} }

View File

@@ -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 {

View File

@@ -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'
const nodeModule = require('module') const nodeModule = require('module')
const nodeRequire = (global as any).require const nodeRequire = (global as any).require
@@ -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 = await fs.readJson(infoPath) 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)
}
} }
} }

View File

@@ -55,13 +55,13 @@ 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',
'fs-promise': 'commonjs fs-promise',
'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',
'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(),

255
app/yarn.lock Normal file
View File

@@ -0,0 +1,255 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@angular/animations@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.0.1.tgz#154420c8ee5c22fbaf1434b6d156150cf5218da6"
"@angular/common@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.0.1.tgz#df488eada842b2d841ded750712292b18387b5b0"
"@angular/compiler@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.0.1.tgz#15721edb148167a2d83b6f9324817e658eac8280"
"@angular/core@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.0.1.tgz#0b110a001012076ea696460ccd922707bcdf51ba"
"@angular/forms@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.0.1.tgz#b9ebdbbb8ace0f9a3bf9e53c299eafdfab1d5041"
"@angular/platform-browser-dynamic@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.0.1.tgz#fd5debb2d3f6474350965e71c2674e2170d7cfcb"
"@angular/platform-browser@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.0.1.tgz#4b9efbeb2fbb900de188743b988802d3aa2b33ff"
"@ng-bootstrap/ng-bootstrap@1.0.0-alpha.22":
version "1.0.0-alpha.22"
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-alpha.22.tgz#aaad058cc39293ea6184e4b9b849f298c0b11a86"
"@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.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.7.tgz#fb0ad04b5b6f6eabe0372a32a8f1fbba5c130cae"
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"
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.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.4.tgz#cc40ae5a1c879601c5ebba2096b5c80f0c4c3602"

View File

@@ -9,8 +9,6 @@ environment:
cache: cache:
- '%USERPROFILE%\.electron' - '%USERPROFILE%\.electron'
skip_tags: true
version: "{build}" version: "{build}"
install: install:
@@ -24,6 +22,8 @@ build_script:
- node scripts/prepackage-plugins.js - node scripts/prepackage-plugins.js
- node scripts/build-windows.js - node scripts/build-windows.js
artifacts:
- path: 'dist\win\*.exe'
deploy: deploy:
provider: GitHub provider: GitHub
auth_token: auth_token:
@@ -31,6 +31,7 @@ deploy:
artifact: /.*\.exe/ artifact: /.*\.exe/
draft: false draft: false
prerelease: false prerelease: false
force_update: true
on: on:
branch: master branch: master
appveyor_repo_tag: true appveyor_repo_tag: true

View File

@@ -1,22 +1,53 @@
<!DOCTYPE html><html><head><link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"><link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"><link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400" rel="stylesheet"><style>body { <!DOCTYPE html><html><head><link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400" rel="stylesheet"><link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"><link href="https://cdn.jsdelivr.net/g/bootstrap@4.0.0-alpha.6(css/bootstrap.min.css)" rel="stylesheet"><script src="https://cdn.jsdelivr.net/g/jquery@3.2.1,tether@1.4.0,bootstrap@4.0.0-alpha.6,modernizr@3.3.1,detectizr@2.2.0"></script><title>Terminus</title><style>body {
font-family: 'Source Sans Pro', sans-serif; font-family: 'Source Sans Pro', sans-serif;
background: #111; background: #111;
color: #ccc; color: #ccc;
min-height: 100vh;
background-image: radial-gradient(#111, #000);
} }
h1 { h1 {
font-size: 64px; font-size: 64px;
} }
h1, h3, h5 { h1, h2, h3, h5 {
font-weight: 300; font-weight: 300;
color: white; color: white;
} }
.btn i + span { .btn i + span,
.nav-link i + span {
margin-left: 10px; margin-left: 10px;
} }
.btn-outline-primary { .btn-outline-primary {
color: #b6e7ff !important; color: #b6e7ff !important;
}</style></head><body><div class="container mt-5 mb-3"><div class="text-center"><h1>Terminus</h1><h5>A terminal for a more modern age</h5><div class="btn-group mt-3 mb-3"><a class="btn btn-outline-primary"><i class="fa fa-windows"></i><span>Windows</span></a><a class="btn btn-outline-primary"><i class="fa fa-apple"></i><span>macOS</span></a><a class="btn btn-outline-primary"><i class="fa fa-archive"></i><span>DEB</span></a><a class="btn btn-outline-primary"><i class="fa fa-archive"></i><span>RPM</span></a><a class="btn btn-outline-primary"><i class="fa fa-archive"></i><span>TGZ</span></a></div></div><div class="row mt-5"><div class="col-6"><h3>Proper Windows experience</h3><p> <b>Clink </b>provides tab completion, readline-style editing and persistent command history on Windows.</p><p>Also supported:<ul> <li>Classic CMD</li><li>PowerShell </li><li>Bash on Windows </li></ul></p></div><div class="col-6"><h3>User experience</h3><ul><li>Spawn and hide with a global hotkey</li><li>Fully customizable hotkey schema</li><li>Restores tabs </li><li>Drag in a file to paste the path</li><li>Click paths and URLs to open in browser/file manager</li><li>Keeps the current directory in new tabs</li></ul></div></div><div class="row mt-5"><div class="col-6"><h3>Customizable</h3><p>Multiple app themes and a myriad of community color schemes for the terminal. Color scheme editor included.</p></div><div class="col-6"><h3>Infinitely extensible</h3><p>Install plugins from the NPM repository, or create your own with Typescript and Angular framework.</p></div></div></div></body></html> }
.nav-link {
font-size: 22px;
}
video, img {
max-width: 100%;
box-shadow: 0 0 50px black;
}
</style><script defer>setTimeout(function () {
/*
if (Detectizr.os.name == 'windows') {
$('[href="#windows"]').tab('show')
}
if (Detectizr.os.name == 'mac os') {
$('[href="#macos"]').tab('show')
}
if (Detectizr.os.name == 'linux') {
$('[href="#linux"]').tab('show')
}
*/
})</script></head><body><div class="container mt-5 mb-5"><div class="text-center"><h1>Terminus</h1><h5>A terminal for a more modern age</h5><h2 class="text-muted">alpha</h2></div><div class="d-flex flex-row mt-5 mb-5"><ul class="nav nav-pills flex-column mr-5" style="min-width: 200px;"><li class="nav-item"><a class="nav-link active" data-toggle="tab" href="#windows" role="tab"><i class="fa fa-windows"></i><span>Windows</span></a></li><li class="nav-item"><a class="nav-link" data-toggle="tab" href="#macos" role="tab"><i class="fa fa-apple"></i><span>macOS</span></a></li><li class="nav-item"><a class="nav-link" data-toggle="tab" href="#linux" role="tab"><i class="fa fa-linux"></i><span>Linux</span></a></li></ul><div class="tab-content"><div class="tab-pane active" id="windows" role="tabpanel"><div class="row"><div class="col-6"><video src="videos/windows.mp4" autoplay loop></video></div><div class="col-6"><h3>A proper Windows experience</h3><p> <b>Clink </b>provides tab completion, readline-style editing and persistent command history on Windows.</p><p>Also supported:<ul> <li>Classic CMD</li><li>PowerShell </li><li>Bash on Windows </li></ul></p></div></div></div><div class="tab-pane" id="macos" role="tabpanel"><div class="row"><div class="col-6"><!--video(src='videos/windows.mp4', autoplay, loop)--></div><div class="col-6"><h3>Well...</h3><p>Not much to say here, it just works.</p></div></div></div><div class="tab-pane" id="linux" role="tabpanel"><div class="row"><div class="col-6"><img src="linux.png"></div><div class="col-6"><p><ul> <li>Spawn with a global hotkey</li><li>Tabs persist after restart</li><li>Auto-dock to any side of any screen</li><li>Full Unicode and double-width character support</li></ul></p></div></div></div></div></div><div class="text-center"><div class="mt-3 mb-3"><h2></h2><div><div class="btn-group mt-3 mb-1"><a class="btn btn-lg btn-outline-success" href="https://github.com/Eugeny/terminus/releases/latest" target="_blank"><i class="fa fa-download"></i><span>Downloads</span></a><a class="btn btn-lg btn-outline-secondary" href="https://github.com/Eugeny/terminus" target="_blank"><i class="fa fa-github"></i><span>GitHub</span></a></div></div><small class="text-muted">EXE, DMG, DEB, RPM, TGZ</small></div></div><div class="row mt-5"><div class="col-6"><h3>User experience</h3><ul><li>Spawn and hide with a global hotkey</li><li>Fully customizable hotkey schema</li><li>Restores tabs </li><li>Drag in a file to paste the path</li><li>Click paths and URLs to open in browser/file manager</li><li>Keeps the current directory in new tabs</li></ul></div><div class="col-6"><div class="mb-5"><h3>Customizable</h3><p>Multiple app themes and a myriad of community color schemes for the terminal. Color scheme editor included.</p></div><div> <h3>Infinitely extensible</h3><p>Install plugins from the NPM repository, or create your own with Typescript and Angular framework.</p></div></div></div></div><script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-3278102-18', 'auto');
ga('send', 'pageview');</script></body></html>

View File

@@ -1,71 +1,136 @@
doctype html doctype html
html html
head head
link(href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css", rel="stylesheet")
link(href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css", rel="stylesheet")
link(href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400", rel="stylesheet") link(href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400", rel="stylesheet")
link(href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css", rel="stylesheet")
link(href="https://cdn.jsdelivr.net/g/bootstrap@4.0.0-alpha.6(css/bootstrap.min.css)", rel="stylesheet")
script(src="https://cdn.jsdelivr.net/g/jquery@3.2.1,tether@1.4.0,bootstrap@4.0.0-alpha.6,modernizr@3.3.1,detectizr@2.2.0")
title Terminus
style. style.
body { body {
font-family: 'Source Sans Pro', sans-serif; font-family: 'Source Sans Pro', sans-serif;
background: #111; background: #111;
color: #ccc; color: #ccc;
min-height: 100vh;
background-image: radial-gradient(#111, #000);
} }
h1 { h1 {
font-size: 64px; font-size: 64px;
} }
h1, h3, h5 { h1, h2, h3, h5 {
font-weight: 300; font-weight: 300;
color: white; color: white;
} }
.btn i + span { .btn i + span,
.nav-link i + span {
margin-left: 10px; margin-left: 10px;
} }
.btn-outline-primary { .btn-outline-primary {
color: #b6e7ff !important; color: #b6e7ff !important;
} }
.nav-link {
font-size: 22px;
}
video, img {
max-width: 100%;
box-shadow: 0 0 50px black;
}
script(defer).
setTimeout(function () {
/*
if (Detectizr.os.name == 'windows') {
$('[href="#windows"]').tab('show')
}
if (Detectizr.os.name == 'mac os') {
$('[href="#macos"]').tab('show')
}
if (Detectizr.os.name == 'linux') {
$('[href="#linux"]').tab('show')
}
*/
})
body body
.container.mt-5.mb-3 .container.mt-5.mb-5
.text-center .text-center
h1 Terminus h1 Terminus
h5 A terminal for a more modern age h5 A terminal for a more modern age
h2.text-muted alpha
.d-flex.flex-row.mt-5.mb-5
ul.nav.nav-pills.flex-column.mr-5(style='min-width: 200px')
li.nav-item
a.nav-link.active(data-toggle='tab', href='#windows', role='tab')
i.fa.fa-windows
span Windows
li.nav-item
a.nav-link(data-toggle='tab', href='#macos', role='tab')
i.fa.fa-apple
span macOS
li.nav-item
a.nav-link(data-toggle='tab', href='#linux', role='tab')
i.fa.fa-linux
span Linux
.tab-content
#windows.tab-pane.active(role='tabpanel')
.row
.col-6
video(src='videos/windows.mp4', autoplay, loop)
.col-6
h3 A proper Windows experience
p
b Clink
| provides tab completion, readline-style editing and persistent command history on Windows.
p Also supported:
ul
li Classic CMD
li PowerShell
li Bash on Windows
#macos.tab-pane(role='tabpanel')
.row
.col-6
//video(src='videos/windows.mp4', autoplay, loop)
.col-6
h3 Well...
p Not much to say here, it just works.
#linux.tab-pane(role='tabpanel')
.row
.col-6
img(src='linux.png')
.col-6
p
ul
li Spawn with a global hotkey
li Tabs persist after restart
li Auto-dock to any side of any screen
li Full Unicode and double-width character support
.btn-group.mt-3.mb-3 .text-center
a.btn.btn-outline-primary .mt-3.mb-3
i.fa.fa-windows h2
span Windows
a.btn.btn-outline-primary
i.fa.fa-apple
span macOS
a.btn.btn-outline-primary div
i.fa.fa-archive .btn-group.mt-3.mb-1
span DEB a.btn.btn-lg.btn-outline-success(href='https://github.com/Eugeny/terminus/releases/latest', target='_blank')
i.fa.fa-download
span Downloads
a.btn.btn-lg.btn-outline-secondary(href='https://github.com/Eugeny/terminus', target='_blank')
i.fa.fa-github
span GitHub
small.text-muted EXE, DMG, DEB, RPM, TGZ
a.btn.btn-outline-primary
i.fa.fa-archive
span RPM
a.btn.btn-outline-primary
i.fa.fa-archive
span TGZ
.row.mt-5 .row.mt-5
.col-6
h3 Proper Windows experience
p
b Clink
| provides tab completion, readline-style editing and persistent command history on Windows.
p Also supported:
ul
li Classic CMD
li PowerShell
li Bash on Windows
.col-6 .col-6
h3 User experience h3 User experience
ul ul
@@ -76,13 +141,20 @@ html
li Click paths and URLs to open in browser/file manager li Click paths and URLs to open in browser/file manager
li Keeps the current directory in new tabs li Keeps the current directory in new tabs
.row.mt-5
.col-6 .col-6
h3 Customizable .mb-5
p Multiple app themes and a myriad of community color schemes for the terminal. Color scheme editor included. h3 Customizable
p Multiple app themes and a myriad of community color schemes for the terminal. Color scheme editor included.
.col-6
h3 Infinitely extensible
p Install plugins from the NPM repository, or create your own with Typescript and Angular framework.
div
h3 Infinitely extensible
p Install plugins from the NPM repository, or create your own with Typescript and Angular framework.
script.
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-3278102-18', 'auto');
ga('send', 'pageview');

BIN
docs/linux.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/videos/windows.mp4 Normal file

Binary file not shown.

View File

@@ -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,8 +20,8 @@
"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",
"pug-html-loader": "1.0.9", "pug-html-loader": "1.0.9",
@@ -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"
} }

View File

@@ -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
View 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('..')
})

View File

@@ -1,9 +1,13 @@
const path = require('path') const path = require('path')
const fs = require('fs') const fs = require('fs')
const childProcess = require('child_process')
const appInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../app/package.json'))) const appInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../app/package.json')))
const pkgInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'))) const pkgInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json')))
exports.version = childProcess.execSync('git describe --tags', {encoding:'utf-8'})
exports.version = exports.version.substring(1, exports.version.length - 1)
exports.builtinPlugins = [ exports.builtinPlugins = [
'terminus-core', 'terminus-core',
'terminus-settings', 'terminus-settings',
@@ -12,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

View File

@@ -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": {

View 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:

View 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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View 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:

View File

@@ -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:

View File

@@ -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:

View 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:

View 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:

View File

@@ -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:

View File

@@ -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": {

View File

@@ -50,8 +50,8 @@ 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[],
@@ -93,7 +93,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(() => {

View File

@@ -15,6 +15,7 @@ div
.part(style='transform: rotateZ(308deg)') .part(style='transform: rotateZ(308deg)')
div div
h1.terminus-title Terminus h1.terminus-title Terminus
span.text-muted α
button.btn.btn-primary.btn-lg.btn-block( button.btn.btn-primary.btn-lg.btn-block(
*ngFor='let button of getButtons()', *ngFor='let button of getButtons()',
@@ -24,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

View File

@@ -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}`)
} }
} }

View File

@@ -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 {

View File

@@ -1,4 +1,4 @@
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 { BaseTabComponent } from '../components/baseTab.component'
@Component({ @Component({
@@ -12,4 +12,10 @@ 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()
@HostListener('auxclick', ['$event']) onClick ($event: MouseEvent): void {
if ($event.which == 2) {
this.closeClicked.emit()
}
}
} }

View File

@@ -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)

View File

@@ -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 + Math.round(display.bounds.width * (1.0 - fill))
} 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 + Math.round(display.bounds.height * (1.0 - fill))
} else { } else {
newBounds.y = display.bounds.y newBounds.y = display.bounds.y
} }

View File

@@ -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,6 @@ export class ElectronService {
} }
remoteRequire (name: string): any { remoteRequire (name: string): any {
return this.electron.remote.require(name) return this.remote.require(name)
} }
} }

View File

@@ -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'
@@ -18,9 +19,10 @@ export class HostAppService {
platform: Platform platform: Platform
nodePlatform: string nodePlatform: string
quitRequested = new EventEmitter<any>() 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
@@ -38,17 +40,18 @@ export class HostAppService {
}[this.nodePlatform] }[this.nodePlatform]
electron.ipcRenderer.on('host:quit-request', () => this.zone.run(() => this.quitRequested.emit())) 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(() => {

View File

@@ -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',

View File

@@ -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)

View File

@@ -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": {
@@ -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": {}
} }

View File

@@ -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'

View File

@@ -0,0 +1,7 @@
import { ConfigProvider } from 'terminus-core'
export class PluginsConfigProvider extends ConfigProvider {
defaults = {
npm: 'npm',
}
}

View File

@@ -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: [

View File

@@ -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)
} }
} }

View File

@@ -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/,

View File

@@ -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": {

View File

@@ -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)
}
}
} }

View File

@@ -73,6 +73,7 @@ export class HotkeyInputModalComponent {
} }
ngOnDestroy () { ngOnDestroy () {
this.hotkeys.clearCurrentKeystrokes()
this.hotkeys.enable() this.hotkeys.enable()
clearInterval(this.keyTimeoutInterval) clearInterval(this.keyTimeoutInterval)
} }

View File

@@ -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'

View File

@@ -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,
@@ -28,6 +29,7 @@ export class SettingsTabComponent extends BaseTabComponent {
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$.next('Settings')
this.scrollable = true this.scrollable = true
this.screens = this.docking.getScreens()
} }
getRecoveryToken (): any { getRecoveryToken (): any {

View File

@@ -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/,

View File

@@ -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": {
@@ -34,10 +37,9 @@
}, },
"dependencies": { "dependencies": {
"font-manager": "0.2.2", "font-manager": "0.2.2",
"fs-promise": "2.0.2", "hterm-umdjs": "1.2.0",
"hterm-umdjs": "1.1.3",
"mz": "^2.6.0", "mz": "^2.6.0",
"node-pty": "0.6.2", "node-pty": "0.6.8",
"winreg": "^1.2.3" "winreg": "^1.2.3"
}, },
"false": {} "false": {}

View File

@@ -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)) {
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 = {}
// 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 }

View File

@@ -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'

View File

@@ -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))
} }

View File

@@ -1,7 +1,8 @@
import { BehaviorSubject, ReplaySubject, Subject, Subscription } from 'rxjs' const dataurl = require('dataurl')
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 +17,15 @@ 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 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>()
@@ -37,6 +40,7 @@ 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[],
@@ -64,6 +68,26 @@ 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()
}
})
} }
getRecoveryToken (): any { getRecoveryToken (): any {
@@ -150,10 +174,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 +220,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,10 +237,15 @@ 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)
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')
@@ -239,16 +276,55 @@ 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;
}
`
}
preferenceManager.set('user-css', dataurl.convert({
data: css,
mimetype: 'text/css',
charset: 'utf8',
}))
this.hterm.setBracketedPaste(config.terminal.bracketedPaste) this.hterm.setBracketedPaste(config.terminal.bracketedPaste)
} }
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.configSubscription.unsubscribe()
this.sessionCloseSubscription.unsubscribe() this.hotkeysSubscription.unsubscribe()
this.size$.complete() if (this.sessionCloseSubscription) {
this.sessionCloseSubscription.unsubscribe()
}
this.resize$.complete() this.resize$.complete()
this.input$.complete() this.input$.complete()
this.output$.complete() this.output$.complete()
@@ -260,6 +336,12 @@ 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()
}
}
private setFontSize () {
preferenceManager.set('font-size', this.config.store.terminal.fontSize * Math.pow(1.1, this.zoom))
} }
} }

View File

@@ -7,12 +7,31 @@ export class TerminalConfigProvider extends ConfigProvider {
bell: 'off', bell: 'off',
bracketedPaste: false, bracketedPaste: false,
background: 'theme', background: 'theme',
ligatures: false,
colorScheme: { colorScheme: {
__nonStructural: true, __nonStructural: true,
foreground: null, name: 'Material',
background: null, foreground: '#eceff1',
cursor: null, background: 'rgba(38, 50, 56, 1)',
colors: [], cursor: '#FFCC00',
colors: [
'#000000',
'#D62341',
'#9ECE58',
'#FAED70',
'#396FE2',
'#BB80B3',
'#2DDAFD',
'#d0d0d0',
'rgba(255, 255, 255, 0.2)',
'#FF5370',
'#C3E88D',
'#FFCB6B',
'#82AAFF',
'#C792EA',
'#89DDFF',
'#ffffff',
]
}, },
customColorSchemes: [] customColorSchemes: []
}, },
@@ -22,9 +41,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'],
@@ -39,6 +75,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'],
@@ -49,9 +102,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'],

View 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',
},
]
}

View File

@@ -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,18 @@ 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
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)
}

View File

@@ -6,10 +6,6 @@ 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;
} }

View File

@@ -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,6 +11,7 @@ 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 { ButtonProvider } from './buttonProvider' import { ButtonProvider } from './buttonProvider'
@@ -19,6 +20,7 @@ import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecora
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,6 +32,7 @@ import { hterm } from './hterm'
], ],
providers: [ providers: [
SessionsService, SessionsService,
ShellsService,
ScreenPersistenceProvider, ScreenPersistenceProvider,
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }, { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true }, { provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
@@ -46,6 +49,7 @@ import { hterm } from './hterm'
}, },
{ 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 },
], ],

View File

@@ -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'
@@ -88,7 +88,7 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
await fs.writeFile(configPath, ` await fs.writeFile(configPath, `
escape ^^^ escape ^^^
vbell on vbell on
deflogin off deflogin on
term xterm-color term xterm-color
bindkey "^[OH" beginning-of-line bindkey "^[OH" beginning-of-line
bindkey "^[OF" end-of-line bindkey "^[OF" end-of-line
@@ -98,9 +98,11 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
defhstatus "^Et" defhstatus "^Et"
hardstatus off hardstatus off
altscreen on altscreen on
defutf8 on
defencoding utf8
`, 'utf-8') `, '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', configPath, '-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 +112,10 @@ 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
}
} }
} }

View File

@@ -1,5 +1,5 @@
import * as nodePTY from 'node-pty' import * as nodePTY from 'node-pty'
import * as fs from 'fs-promise' 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 } from 'terminus-core'
@@ -25,12 +25,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 +66,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,11 +86,15 @@ 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) {
@@ -118,7 +136,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`)

View 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]
}
}
}

View File

@@ -38,10 +38,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/,

4544
yarn.lock Normal file

File diff suppressed because it is too large Load Diff