Compare commits
31 Commits
v1.0.0-alp
...
v1.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7e7d537868 | ||
![]() |
1afb1e718b | ||
![]() |
f71f518058 | ||
![]() |
7a005132cc | ||
![]() |
34ef809aee | ||
![]() |
6352f22c48 | ||
![]() |
d0f378764f | ||
![]() |
7885badbfd | ||
![]() |
5999d169bc | ||
![]() |
40b0f8cb69 | ||
![]() |
f428be5ae7 | ||
![]() |
39183b1205 | ||
![]() |
36f82545ae | ||
![]() |
1ef8343ea9 | ||
![]() |
c9e24819ae | ||
![]() |
e2f0ceef19 | ||
![]() |
acd6995bcc | ||
![]() |
ca5e6079bc | ||
![]() |
48ad16946b | ||
![]() |
0a8af12a93 | ||
![]() |
7e602a3612 | ||
![]() |
c880db21a1 | ||
![]() |
26e212ff2f | ||
![]() |
cdc7daf029 | ||
![]() |
41b6e1d54e | ||
![]() |
1c62f3074c | ||
![]() |
514fdbfb6a | ||
![]() |
466d862caa | ||
![]() |
1f825b16c1 | ||
![]() |
17ad43bf65 | ||
![]() |
c957ebabda |
18
README.md
@@ -1,7 +1,16 @@
|
|||||||
# Terminus α
|
<div align="center">
|
||||||
*A terminal for a more modern age*
|
<img src="https://raw.githubusercontent.com/Eugeny/terminus/master/build/icons/128x128.png">
|
||||||
|
<h1>Terminus α</h1>
|
||||||
|
<p>
|
||||||
|
<i>A terminal for a more modern age</i>
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
[](https://travis-ci.org/Eugeny/terminus) [](https://ci.appveyor.com/project/Eugeny/terminus) [](https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE) [](https://github.com/Eugeny/terminus/releases/latest)
|
[](https://travis-ci.org/Eugeny/terminus) [](https://ci.appveyor.com/project/Eugeny/terminus) [](https://raw.githubusercontent.com/Eugeny/terminus/master/LICENSE) [](https://github.com/Eugeny/terminus/releases/latest)
|
||||||
|
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FEugeny%2Fterminus?ref=badge_shield)
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -28,6 +37,7 @@ 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
|
* [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
|
* [theme-hype](https://github.com/Eugeny/terminus-theme-hype) - a Hyper inspired theme
|
||||||
|
* [shell-selector](https://github.com/Eugeny/terminus-shell-selector) - a quick shell selector pane
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -36,3 +46,7 @@ Plugins can be installed directly from the Settings view inside Terminus.
|
|||||||
Pull requests and plugins are welcome! Publish your plugin on NPM with a `terminus-plugin` keyword to make them appear in the Plugin Manager.
|
Pull requests and plugins are welcome! Publish your plugin on NPM with a `terminus-plugin` keyword to make them appear in the Plugin Manager.
|
||||||
|
|
||||||
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) for a very brief plugin development tutorial!
|
See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) for a very brief plugin development tutorial!
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FEugeny%2Fterminus?ref=badge_large)
|
@@ -13,22 +13,9 @@ html
|
|||||||
app-root
|
app-root
|
||||||
.preload-logo
|
.preload-logo
|
||||||
div
|
div
|
||||||
.terminus-logo.animated
|
.terminus-logo
|
||||||
.part(style='transform: rotateZ(0deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(51deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(102deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(154deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(205deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(257deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(308deg)')
|
|
||||||
div
|
|
||||||
h1.terminus-title Terminus
|
h1.terminus-title Terminus
|
||||||
|
sup α
|
||||||
.progress
|
.progress
|
||||||
.bar(style='width: 0%')
|
.bar(style='width: 0%')
|
||||||
|
|
||||||
|
@@ -192,7 +192,6 @@ start = () => {
|
|||||||
let options = {
|
let options = {
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
//icon: `${app.getAppPath()}/assets/img/icon.png`,
|
|
||||||
title: 'Terminus',
|
title: 'Terminus',
|
||||||
minWidth: 400,
|
minWidth: 400,
|
||||||
minHeight: 300,
|
minHeight: 300,
|
||||||
|
89
app/src/logo.svg
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="150mm"
|
||||||
|
height="150mm"
|
||||||
|
viewBox="0 0 150 150"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.1 r15371"
|
||||||
|
sodipodi:docname="logo.svg"
|
||||||
|
inkscape:export-filename="/home/eugene/Work/term/build/icons/16x16.png"
|
||||||
|
inkscape:export-xdpi="2.7093334"
|
||||||
|
inkscape:export-ydpi="2.7093334">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.49497475"
|
||||||
|
inkscape:cx="134.39743"
|
||||||
|
inkscape:cy="340.43068"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:snap-bbox="true"
|
||||||
|
inkscape:window-width="1366"
|
||||||
|
inkscape:window-height="692"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-10.356544,-82.309525)">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path138"
|
||||||
|
style="opacity:0.9;fill:#ccccff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 39.305965,108.47713 60.922105,35.13225 0.0945,21.68327 -61.016595,-37.11662 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path116"
|
||||||
|
style="opacity:0.9;fill:#6666cc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 136.19445,144.4429 0.0455,20.67266 -78.028381,44.11611 -0.0031,-19.78119 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path118"
|
||||||
|
style="opacity:0.9;fill:#ccccff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 39.471179,178.6501 18.737341,10.818 0.0031,19.78099 -18.740409,-10.88245 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="opacity:0.9;fill:#b4e2ff;fill-rule:evenodd;stroke:none;stroke-width:1.00546169px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 56.43263,98.242186 -17.391087,10.041014 61.186527,35.32618 -61.020778,35.23005 18.839694,10.87703 61.020784,-35.23005 17.39108,-10.04102 z"
|
||||||
|
id="path134"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
@@ -108,8 +108,9 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundPlugins.some(x => x.name === pluginName)) {
|
if (foundPlugins.some(x => x.name === pluginName.substring('terminus-'.length))) {
|
||||||
console.info(`Plugin ${pluginName} already exists`)
|
console.info(`Plugin ${pluginName} already exists, overriding`)
|
||||||
|
foundPlugins = foundPlugins.filter(x => x.name !== pluginName.substring('terminus-'.length))
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -1,6 +1,3 @@
|
|||||||
$color: rgba(66, 142, 173, 0.75);
|
|
||||||
|
|
||||||
|
|
||||||
.preload-logo {
|
.preload-logo {
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -24,7 +21,7 @@ $color: rgba(66, 142, 173, 0.75);
|
|||||||
|
|
||||||
.bar {
|
.bar {
|
||||||
transition: 1s ease-out width;
|
transition: 1s ease-out width;
|
||||||
background: $color;
|
background: #a1c5e4;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,63 +39,22 @@ $color: rgba(66, 142, 173, 0.75);
|
|||||||
.terminus-logo {
|
.terminus-logo {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
height: 160px;
|
height: 160px;
|
||||||
|
background: url('./logo.svg');
|
||||||
|
background-repeat: none;
|
||||||
|
background-size: contain;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
position: relative;
|
|
||||||
transform: rotateZ(-14.5deg);
|
|
||||||
|
|
||||||
.part {
|
|
||||||
position: absolute;
|
|
||||||
width: 160px;
|
|
||||||
height: 160px;
|
|
||||||
|
|
||||||
div {
|
|
||||||
position: absolute;
|
|
||||||
top: 33px;
|
|
||||||
left: 24px;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
background: $color;
|
|
||||||
transform: rotateX(52deg) rotateY(-42deg);
|
|
||||||
animation: terminusLogoPartOnce ease-out 1s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.animated .part div {
|
|
||||||
animation: terminusLogoPart infinite ease-out 2s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.terminus-title {
|
.terminus-title {
|
||||||
color: $color;
|
color: #a1c5e4;
|
||||||
font-family: 'Source Sans Pro';
|
font-family: 'Source Sans Pro';
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
|
||||||
|
|
||||||
|
sup {
|
||||||
@keyframes terminusLogoPart {
|
color: #842fe0;
|
||||||
0% {
|
|
||||||
transform: rotateX(90deg) rotateY(-90deg);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
transform: rotateX(52deg) rotateY(-42deg);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
transform: rotateX(52deg) rotateY(-42deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotateX(-90deg) rotateY(-90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes terminusLogoPartOnce {
|
|
||||||
0% {
|
|
||||||
transform: rotateX(90deg) rotateY(-90deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotateX(52deg) rotateY(-42deg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 2.4 KiB |
118
build/icons/icon.svg
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="150mm"
|
||||||
|
height="150mm"
|
||||||
|
viewBox="0 0 150 150"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.1 r15371"
|
||||||
|
sodipodi:docname="icon.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient4649">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000916;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop4645" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#004565;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop4647" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient4649"
|
||||||
|
id="linearGradient4651"
|
||||||
|
x1="89.26284"
|
||||||
|
y1="85.146751"
|
||||||
|
x2="89.26284"
|
||||||
|
y2="229.47229"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.49497475"
|
||||||
|
inkscape:cx="134.39743"
|
||||||
|
inkscape:cy="340.43068"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:snap-bbox="true"
|
||||||
|
inkscape:window-width="1366"
|
||||||
|
inkscape:window-height="692"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-10.356544,-82.309525)">
|
||||||
|
<rect
|
||||||
|
id="rect168"
|
||||||
|
width="150"
|
||||||
|
height="150"
|
||||||
|
x="10.356544"
|
||||||
|
y="82.309525"
|
||||||
|
style="fill:url(#linearGradient4651);fill-opacity:1;stroke-width:0.26458332"
|
||||||
|
rx="10"
|
||||||
|
ry="10" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path138"
|
||||||
|
style="opacity:0.9;fill:#ccccff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 39.305965,108.47713 60.922105,35.13225 0.0945,21.68327 -61.016595,-37.11662 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path116"
|
||||||
|
style="opacity:0.9;fill:#6666cc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 136.19445,144.4429 0.0455,20.67266 -78.028381,44.11611 -0.0031,-19.78119 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path118"
|
||||||
|
style="opacity:0.9;fill:#ccccff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 39.471179,178.6501 18.737341,10.818 0.0031,19.78099 -18.740409,-10.88245 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="opacity:0.9;fill:#b4e2ff;fill-rule:evenodd;stroke:none;stroke-width:1.00546169px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m 56.43263,98.242186 -17.391087,10.041014 61.186527,35.32618 -61.020778,35.23005 18.839694,10.87703 61.020784,-35.23005 17.39108,-10.04102 z"
|
||||||
|
id="path134"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 361 KiB |
@@ -74,7 +74,8 @@
|
|||||||
"libnotify4",
|
"libnotify4",
|
||||||
"libappindicator1",
|
"libappindicator1",
|
||||||
"libxtst6",
|
"libxtst6",
|
||||||
"libnss3"
|
"libnss3",
|
||||||
|
"tmux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"rpm": {
|
"rpm": {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-community-color-schemes",
|
"name": "terminus-community-color-schemes",
|
||||||
"version": "1.0.0-alpha.16-8-gfc060ac",
|
"version": "1.0.0-alpha.24",
|
||||||
"description": "Community color schemes for Terminus",
|
"description": "Community color schemes for Terminus",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-plugin"
|
"terminus-plugin"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-core",
|
"name": "terminus-core",
|
||||||
"version": "1.0.0-alpha.16-8-gfc060ac",
|
"version": "1.0.0-alpha.24",
|
||||||
"description": "Terminus core",
|
"description": "Terminus core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-plugin"
|
"terminus-plugin"
|
||||||
|
@@ -1,27 +1,14 @@
|
|||||||
div
|
div
|
||||||
.terminus-logo
|
.terminus-logo
|
||||||
.part(style='transform: rotateZ(0deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(51deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(102deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(154deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(205deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(257deg)')
|
|
||||||
div
|
|
||||||
.part(style='transform: rotateZ(308deg)')
|
|
||||||
div
|
|
||||||
h1.terminus-title Terminus
|
h1.terminus-title Terminus
|
||||||
span.text-muted α
|
sup α
|
||||||
|
|
||||||
button.btn.btn-primary.btn-lg.btn-block(
|
.list-group
|
||||||
|
a.list-group-item.list-group-item-action(
|
||||||
*ngFor='let button of getButtons()',
|
*ngFor='let button of getButtons()',
|
||||||
(click)='button.click()',
|
(click)='button.click()',
|
||||||
)
|
)
|
||||||
i.fa([class]='"fa fa-" + button.icon')
|
i([class]='"fa fa-fw fa-" + button.icon')
|
||||||
span {{button.title}}
|
span {{button.title}}
|
||||||
|
|
||||||
footer
|
footer
|
||||||
|
@@ -24,6 +24,6 @@ footer {
|
|||||||
background: rgba(0,0,0,.5);
|
background: rgba(0,0,0,.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
a, button {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@ export class ConfigProxy {
|
|||||||
{
|
{
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: false,
|
configurable: false,
|
||||||
get: () => real[key] || defaults[key],
|
get: () => (real[key] !== undefined) ? real[key] : defaults[key],
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
real[key] = value
|
real[key] = value
|
||||||
}
|
}
|
||||||
|
@@ -5,14 +5,15 @@ export class Logger {
|
|||||||
private name: string,
|
private name: string,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
log (level: string, ...args: any[]) {
|
doLog (level: string, ...args: any[]) {
|
||||||
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
debug (...args: any[]) { this.log('debug', ...args) }
|
debug (...args: any[]) { this.doLog('debug', ...args) }
|
||||||
info (...args: any[]) { this.log('info', ...args) }
|
info (...args: any[]) { this.doLog('info', ...args) }
|
||||||
warn (...args: any[]) { this.log('warn', ...args) }
|
warn (...args: any[]) { this.doLog('warn', ...args) }
|
||||||
error (...args: any[]) { this.log('error', ...args) }
|
error (...args: any[]) { this.doLog('error', ...args) }
|
||||||
|
log (...args: any[]) { this.doLog('log', ...args) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@@ -71,7 +71,11 @@ $dropdown-link-disabled-color: #333;
|
|||||||
$dropdown-header-color: #333;
|
$dropdown-header-color: #333;
|
||||||
|
|
||||||
$list-group-color: $body-color;
|
$list-group-color: $body-color;
|
||||||
$list-group-bg: $body-bg2;
|
$list-group-bg: rgba(255,255,255,.05);
|
||||||
|
$list-group-border-color: rgba(255,255,255,.1);
|
||||||
|
$list-group-hover-bg: rgba(255,255,255,.1);
|
||||||
|
$list-group-link-active-bg: rgba(255,255,255,.2);
|
||||||
|
|
||||||
|
|
||||||
@import '~bootstrap/scss/bootstrap.scss';
|
@import '~bootstrap/scss/bootstrap.scss';
|
||||||
|
|
||||||
@@ -271,12 +275,6 @@ hotkey-input-modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start-page {
|
|
||||||
.terminus-title {
|
|
||||||
color: $blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
.form-group label {
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
@@ -314,3 +312,11 @@ ngb-tabset .tab-content {
|
|||||||
.input-group > select.form-control {
|
.input-group > select.form-control {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-group-item {
|
||||||
|
transition: 0.25s background;
|
||||||
|
|
||||||
|
i + * {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-plugin-manager",
|
"name": "terminus-plugin-manager",
|
||||||
"version": "1.0.0-alpha.16-8-gfc060ac",
|
"version": "1.0.0-alpha.24",
|
||||||
"description": "Terminus' plugin manager",
|
"description": "Terminus' plugin manager",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-plugin"
|
"terminus-plugin"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-settings",
|
"name": "terminus-settings",
|
||||||
"version": "1.0.0-alpha.16-8-gfc060ac",
|
"version": "1.0.0-alpha.24",
|
||||||
"description": "Terminus terminal settings page",
|
"description": "Terminus terminal settings page",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-plugin"
|
"terminus-plugin"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "terminus-terminal",
|
"name": "terminus-terminal",
|
||||||
"version": "1.0.0-alpha.16-8-gfc060ac",
|
"version": "1.0.0-alpha.24",
|
||||||
"description": "Terminus' terminal emulation core",
|
"description": "Terminus' terminal emulation core",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"terminus-plugin"
|
"terminus-plugin"
|
||||||
|
@@ -28,6 +28,10 @@ export interface SessionOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class SessionPersistenceProvider {
|
export abstract class SessionPersistenceProvider {
|
||||||
|
abstract id: string
|
||||||
|
abstract displayName: string
|
||||||
|
|
||||||
|
abstract isAvailable (): boolean
|
||||||
abstract async attachSession (recoveryId: any): Promise<SessionOptions>
|
abstract async attachSession (recoveryId: any): Promise<SessionOptions>
|
||||||
abstract async startSession (options: SessionOptions): Promise<any>
|
abstract async startSession (options: SessionOptions): Promise<any>
|
||||||
abstract async terminateSession (recoveryId: string): Promise<void>
|
abstract async terminateSession (recoveryId: string): Promise<void>
|
||||||
@@ -44,3 +48,15 @@ export interface ITerminalColorScheme {
|
|||||||
export abstract class TerminalColorSchemeProvider {
|
export abstract class TerminalColorSchemeProvider {
|
||||||
abstract async getSchemes (): Promise<ITerminalColorScheme[]>
|
abstract async getSchemes (): Promise<ITerminalColorScheme[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IShell {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
command: string
|
||||||
|
args?: string[]
|
||||||
|
env?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ShellProvider {
|
||||||
|
abstract async provide (): Promise<IShell[]>
|
||||||
|
}
|
||||||
|
@@ -1,24 +1,32 @@
|
|||||||
|
import { AsyncSubject } from 'rxjs'
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, AppService, ConfigService, HostAppService, Platform, ElectronService } from 'terminus-core'
|
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, ConfigService, HostAppService, ElectronService, Logger, LogService } from 'terminus-core'
|
||||||
|
|
||||||
import { SessionsService } from './services/sessions.service'
|
import { IShell, ShellProvider } from './api'
|
||||||
import { ShellsService } from './services/shells.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ButtonProvider extends ToolbarButtonProvider {
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
|
private shells$ = new AsyncSubject<IShell[]>()
|
||||||
|
private logger: Logger
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private terminal: TerminalService,
|
||||||
private sessions: SessionsService,
|
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private shells: ShellsService,
|
log: LogService,
|
||||||
private hostApp: HostAppService,
|
hostApp: HostAppService,
|
||||||
|
@Inject(ShellProvider) shellProviders: ShellProvider[],
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
this.logger = log.create('newTerminalButton')
|
||||||
|
Promise.all(shellProviders.map(x => x.provide())).then(shellLists => {
|
||||||
|
this.shells$.next(shellLists.reduce((a, b) => a.concat(b)))
|
||||||
|
this.shells$.complete()
|
||||||
|
})
|
||||||
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
||||||
if (hotkey === 'new-tab') {
|
if (hotkey === 'new-tab') {
|
||||||
this.openNewTab()
|
this.openNewTab()
|
||||||
@@ -47,31 +55,9 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async openNewTab (cwd?: string): Promise<void> {
|
async openNewTab (cwd?: string): Promise<void> {
|
||||||
if (!cwd && this.app.activeTab instanceof TerminalTabComponent) {
|
let shells = await this.shells$.first().toPromise()
|
||||||
cwd = await this.app.activeTab.session.getWorkingDirectory()
|
let shell = shells.find(x => x.id === this.config.store.terminal.shell) || shells[0]
|
||||||
}
|
this.terminal.openTab(shell, cwd)
|
||||||
let command = this.config.store.terminal.shell
|
|
||||||
let env: any = process.env
|
|
||||||
let args: string[] = []
|
|
||||||
if (command === '~clink~') {
|
|
||||||
({ command, args } = this.shells.getClinkOptions())
|
|
||||||
}
|
|
||||||
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(
|
|
||||||
TerminalTabComponent,
|
|
||||||
{ sessionOptions }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
provide (): IToolbarButton[] {
|
provide (): IToolbarButton[] {
|
||||||
|
@@ -208,19 +208,19 @@
|
|||||||
type='radio',
|
type='radio',
|
||||||
[value]='"block"'
|
[value]='"block"'
|
||||||
)
|
)
|
||||||
| Block
|
| █
|
||||||
label.btn.btn-secondary
|
|
||||||
input(
|
|
||||||
type='radio',
|
|
||||||
[value]='"underline"'
|
|
||||||
)
|
|
||||||
| Underline
|
|
||||||
label.btn.btn-secondary
|
label.btn.btn-secondary
|
||||||
input(
|
input(
|
||||||
type='radio',
|
type='radio',
|
||||||
[value]='"beam"'
|
[value]='"beam"'
|
||||||
)
|
)
|
||||||
| Beam
|
| |
|
||||||
|
label.btn.btn-secondary
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
[value]='"underline"'
|
||||||
|
)
|
||||||
|
| ▁
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Shell
|
label Shell
|
||||||
@@ -230,7 +230,7 @@
|
|||||||
)
|
)
|
||||||
option(
|
option(
|
||||||
*ngFor='let shell of shells',
|
*ngFor='let shell of shells',
|
||||||
[ngValue]='shell.command'
|
[ngValue]='shell.id'
|
||||||
) {{shell.name}}
|
) {{shell.name}}
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
@@ -259,3 +259,15 @@
|
|||||||
[value]='"audible"'
|
[value]='"audible"'
|
||||||
)
|
)
|
||||||
| Audible
|
| Audible
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Session persistence
|
||||||
|
select.form-control(
|
||||||
|
'[(ngModel)]'='config.store.terminal.persistence',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
option([ngValue]='null') Off
|
||||||
|
option(
|
||||||
|
*ngFor='let provider of persistenceProviders',
|
||||||
|
[ngValue]='provider.id'
|
||||||
|
) {{provider.displayName}}
|
||||||
|
@@ -1,23 +1,11 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import * as fs from 'mz/fs'
|
|
||||||
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')
|
||||||
const fontManager = require('font-manager')
|
const fontManager = require('font-manager')
|
||||||
|
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
import { ConfigService, HostAppService, Platform } from 'terminus-core'
|
import { ConfigService, HostAppService, Platform } from 'terminus-core'
|
||||||
import { TerminalColorSchemeProvider, ITerminalColorScheme } from '../api'
|
import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvider, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
let Registry = null
|
|
||||||
try {
|
|
||||||
Registry = require('winreg')
|
|
||||||
} catch (_) { } // tslint:disable-line no-empty
|
|
||||||
|
|
||||||
interface IShell {
|
|
||||||
name: string
|
|
||||||
command: string
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: require('./terminalSettingsTab.component.pug'),
|
template: require('./terminalSettingsTab.component.pug'),
|
||||||
@@ -26,6 +14,7 @@ interface IShell {
|
|||||||
export class TerminalSettingsTabComponent {
|
export class TerminalSettingsTabComponent {
|
||||||
fonts: string[] = []
|
fonts: string[] = []
|
||||||
shells: IShell[] = []
|
shells: IShell[] = []
|
||||||
|
persistenceProviders: SessionPersistenceProvider[]
|
||||||
colorSchemes: ITerminalColorScheme[] = []
|
colorSchemes: ITerminalColorScheme[] = []
|
||||||
equalComparator = equal
|
equalComparator = equal
|
||||||
editingColorScheme: ITerminalColorScheme
|
editingColorScheme: ITerminalColorScheme
|
||||||
@@ -34,8 +23,12 @@ export class TerminalSettingsTabComponent {
|
|||||||
constructor (
|
constructor (
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
|
@Inject(ShellProvider) private shellProviders: ShellProvider[],
|
||||||
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
||||||
) { }
|
@Inject(SessionPersistenceProvider) persistenceProviders: SessionPersistenceProvider[],
|
||||||
|
) {
|
||||||
|
this.persistenceProviders = persistenceProviders.filter(x => x.isAvailable())
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) {
|
||||||
@@ -53,71 +46,8 @@ export class TerminalSettingsTabComponent {
|
|||||||
this.fonts.sort()
|
this.fonts.sort()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.hostApp.platform === Platform.Windows) {
|
|
||||||
this.shells = [
|
|
||||||
{ name: 'CMD (clink)', command: '~clink~' },
|
|
||||||
{ name: 'CMD (stock)', command: 'cmd.exe' },
|
|
||||||
{ name: 'PowerShell', command: 'powershell.exe' },
|
|
||||||
]
|
|
||||||
|
|
||||||
// Detect whether BoW is installed
|
|
||||||
const wslPath = `${process.env.windir}\\system32\\bash.exe`
|
|
||||||
if (await fs.exists(wslPath)) {
|
|
||||||
this.shells.push({ name: 'Bash on Windows', command: wslPath })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect Cygwin
|
|
||||||
let cygwinPath = await new Promise<string>(resolve => {
|
|
||||||
let reg = new Registry({ hive: Registry.HKLM, key: '\\Software\\Cygwin\\setup', arch: 'x64' })
|
|
||||||
reg.get('rootdir', (err, item) => {
|
|
||||||
if (err) {
|
|
||||||
return resolve(null)
|
|
||||||
}
|
|
||||||
resolve(item.value)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if (cygwinPath) {
|
|
||||||
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) {
|
|
||||||
this.shells = [{ name: 'Default shell', command: '~default-shell~' }]
|
|
||||||
this.shells = this.shells.concat((await fs.readFile('/etc/shells', { encoding: 'utf-8' }))
|
|
||||||
.split('\n')
|
|
||||||
.map(x => x.trim())
|
|
||||||
.filter(x => x && !x.startsWith('#'))
|
|
||||||
.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))
|
||||||
|
this.shells = (await Promise.all(this.shellProviders.map(x => x.provide()))).reduce((a, b) => a.concat(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
fontAutocomplete = (text$: Observable<string>) => {
|
fontAutocomplete = (text$: Observable<string>) => {
|
||||||
|
@@ -214,6 +214,13 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _measureCharacterSize = hterm.scrollPort_.measureCharacterSize.bind(hterm.scrollPort_)
|
||||||
|
hterm.scrollPort_.measureCharacterSize = () => {
|
||||||
|
let size = _measureCharacterSize()
|
||||||
|
size.height += this.config.store.terminal.linePadding
|
||||||
|
return size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachIOHandlers (io: any) {
|
attachIOHandlers (io: any) {
|
||||||
|
@@ -4,6 +4,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
defaults = {
|
defaults = {
|
||||||
terminal: {
|
terminal: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
linePadding: 0,
|
||||||
bell: 'off',
|
bell: 'off',
|
||||||
bracketedPaste: false,
|
bracketedPaste: false,
|
||||||
background: 'theme',
|
background: 'theme',
|
||||||
@@ -42,7 +43,8 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
[Platform.macOS]: {
|
[Platform.macOS]: {
|
||||||
terminal: {
|
terminal: {
|
||||||
font: 'Menlo',
|
font: 'Menlo',
|
||||||
shell: '~default-shell~',
|
shell: 'default',
|
||||||
|
persistence: 'screen',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
'copy': [
|
'copy': [
|
||||||
@@ -73,7 +75,8 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
[Platform.Windows]: {
|
[Platform.Windows]: {
|
||||||
terminal: {
|
terminal: {
|
||||||
font: 'Consolas',
|
font: 'Consolas',
|
||||||
shell: '~clink~',
|
shell: 'clink',
|
||||||
|
persistence: null,
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
'copy': [
|
'copy': [
|
||||||
@@ -103,7 +106,8 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
[Platform.Linux]: {
|
[Platform.Linux]: {
|
||||||
terminal: {
|
terminal: {
|
||||||
font: 'Liberation Mono',
|
font: 'Liberation Mono',
|
||||||
shell: '~default-shell~',
|
shell: 'default',
|
||||||
|
persistence: 'tmux',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
'copy': [
|
'copy': [
|
||||||
|
@@ -71,5 +71,7 @@ const _collapseToEnd = Selection.prototype.collapseToEnd
|
|||||||
Selection.prototype.collapseToEnd = function () {
|
Selection.prototype.collapseToEnd = function () {
|
||||||
try {
|
try {
|
||||||
_collapseToEnd.apply(this)
|
_collapseToEnd.apply(this)
|
||||||
} catch (err) { ; }
|
} catch (err) {
|
||||||
|
// tslint-disable-line
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,11 @@ x-screen {
|
|||||||
transition: 0.125s ease background;
|
transition: 0.125s ease background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x-row > span {
|
||||||
|
display: inline-block;
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "monospace-fallback";
|
font-family: "monospace-fallback";
|
||||||
src: url(fonts/Meslo.otf) format("opentype");
|
src: url(fonts/Meslo.otf) format("opentype");
|
||||||
|
@@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser'
|
|||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
import { HostAppService, Platform, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider } from 'terminus-core'
|
import { 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,18 +11,28 @@ 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 { TerminalService } from './services/terminal.service'
|
||||||
|
|
||||||
import { ScreenPersistenceProvider } from './persistenceProviders'
|
import { ScreenPersistenceProvider } from './persistence/screen'
|
||||||
import { TMuxPersistenceProvider } from './tmux'
|
import { TMuxPersistenceProvider } from './persistence/tmux'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { RecoveryProvider } from './recoveryProvider'
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator } from './api'
|
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator, ShellProvider } from './api'
|
||||||
import { TerminalSettingsTabProvider } from './settings'
|
import { TerminalSettingsTabProvider } from './settings'
|
||||||
import { PathDropDecorator } from './pathDrop'
|
import { PathDropDecorator } from './pathDrop'
|
||||||
import { TerminalConfigProvider } from './config'
|
import { TerminalConfigProvider } from './config'
|
||||||
import { TerminalHotkeyProvider } from './hotkeys'
|
import { TerminalHotkeyProvider } from './hotkeys'
|
||||||
import { HyperColorSchemes } from './colorSchemes'
|
import { HyperColorSchemes } from './colorSchemes'
|
||||||
|
|
||||||
|
import { Cygwin32ShellProvider } from './shells/cygwin32'
|
||||||
|
import { Cygwin64ShellProvider } from './shells/cygwin64'
|
||||||
|
import { GitBashShellProvider } from './shells/gitBash'
|
||||||
|
import { LinuxDefaultShellProvider } from './shells/linuxDefault'
|
||||||
|
import { MacOSDefaultShellProvider } from './shells/macDefault'
|
||||||
|
import { POSIXShellsProvider } from './shells/posix'
|
||||||
|
import { WindowsStockShellsProvider } from './shells/windowsStock'
|
||||||
|
import { WSLShellProvider } from './shells/wsl'
|
||||||
|
|
||||||
import { hterm } from './hterm'
|
import { hterm } from './hterm'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -33,36 +43,27 @@ import { hterm } from './hterm'
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SessionsService,
|
SessionsService,
|
||||||
ShellsService,
|
TerminalService,
|
||||||
ScreenPersistenceProvider,
|
|
||||||
TMuxPersistenceProvider,
|
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
{
|
|
||||||
provide: SessionPersistenceProvider,
|
|
||||||
useFactory: (
|
|
||||||
hostApp: HostAppService,
|
|
||||||
screen: ScreenPersistenceProvider,
|
|
||||||
tmux: TMuxPersistenceProvider,
|
|
||||||
) => {
|
|
||||||
if (hostApp.platform === Platform.Windows) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
if (tmux.isAvailable()) {
|
|
||||||
tmux.init()
|
|
||||||
return tmux
|
|
||||||
} else {
|
|
||||||
return screen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deps: [HostAppService, ScreenPersistenceProvider, TMuxPersistenceProvider],
|
|
||||||
},
|
|
||||||
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
|
||||||
{ provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
|
{ provide: 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 },
|
||||||
|
|
||||||
|
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider, multi: true },
|
||||||
|
{ provide: SessionPersistenceProvider, useClass: TMuxPersistenceProvider, multi: true },
|
||||||
|
|
||||||
|
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: GitBashShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: POSIXShellsProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: WSLShellProvider, multi: true },
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
@@ -104,3 +105,4 @@ export default class TerminalModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from './api'
|
export * from './api'
|
||||||
|
export { TerminalService }
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
|
import * as path from 'path'
|
||||||
import { exec, spawn } from 'mz/child_process'
|
import { exec, spawn } from 'mz/child_process'
|
||||||
import { exec as execCallback } from 'child_process'
|
import { exec as execAsync, execFileSync } from 'child_process'
|
||||||
|
|
||||||
import { AsyncSubject } from 'rxjs'
|
import { AsyncSubject } from 'rxjs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Logger, LogService } from 'terminus-core'
|
import { Logger, LogService, ElectronService } from 'terminus-core'
|
||||||
import { SessionOptions, SessionPersistenceProvider } from './api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
declare function delay (ms: number): Promise<void>
|
declare function delay (ms: number): Promise<void>
|
||||||
|
|
||||||
@@ -29,18 +30,30 @@ async function listProcesses (): Promise<IChildProcess[]> {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||||
|
id = 'screen'
|
||||||
|
displayName = 'GNU Screen'
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
log: LogService,
|
log: LogService,
|
||||||
|
private electron: ElectronService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.logger = log.create('main')
|
this.logger = log.create('main')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAvailable () {
|
||||||
|
try {
|
||||||
|
execFileSync('sh', ['-c', 'which screen'])
|
||||||
|
return true
|
||||||
|
} catch (_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||||
let lines = await new Promise<string[]>(resolve => {
|
let lines = await new Promise<string[]>(resolve => {
|
||||||
execCallback('screen -list', (_err, stdout) => {
|
execAsync('screen -list', (_err, stdout) => {
|
||||||
// returns an error code on macOS
|
// returns an error code on macOS
|
||||||
resolve(stdout.split('\n'))
|
resolve(stdout.split('\n'))
|
||||||
})
|
})
|
||||||
@@ -104,7 +117,7 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async prepareConfig (): Promise<string> {
|
private async prepareConfig (): Promise<string> {
|
||||||
let configPath = '/tmp/.termScreenConfig'
|
let configPath = path.join(this.electron.app.getPath('userData'), 'screen-config.tmp')
|
||||||
await fs.writeFile(configPath, `
|
await fs.writeFile(configPath, `
|
||||||
escape ^^^
|
escape ^^^
|
||||||
vbell off
|
vbell off
|
@@ -1,9 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { execFileSync } from 'child_process'
|
import { execFileSync } from 'child_process'
|
||||||
import * as AsyncLock from 'async-lock'
|
import * as AsyncLock from 'async-lock'
|
||||||
import { ConnectableObservable, Subject } from 'rxjs'
|
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
||||||
import * as childProcess from 'child_process'
|
import * as childProcess from 'child_process'
|
||||||
import { SessionOptions, SessionPersistenceProvider } from './api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
const TMUX_CONFIG = `
|
const TMUX_CONFIG = `
|
||||||
set -g status off
|
set -g status off
|
||||||
@@ -15,6 +15,7 @@ const TMUX_CONFIG = `
|
|||||||
set -g set-titles-string "#W"
|
set -g set-titles-string "#W"
|
||||||
set -g window-status-format '#I:#(pwd="#{pane_current_path}"; echo \${pwd####*/})#F'
|
set -g window-status-format '#I:#(pwd="#{pane_current_path}"; echo \${pwd####*/})#F'
|
||||||
set -g window-status-current-format '#I:#(pwd="#{pane_current_path}"; echo \${pwd####*/})#F'
|
set -g window-status-current-format '#I:#(pwd="#{pane_current_path}"; echo \${pwd####*/})#F'
|
||||||
|
set-option -g prefix C-^
|
||||||
set-option -g status-interval 1
|
set-option -g status-interval 1
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -156,17 +157,33 @@ export class TMux {
|
|||||||
return block.lines
|
return block.lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPID (id: string): Promise<number|null> {
|
||||||
|
let response = await this.process.command(`list-panes -t ${id} -F "#{pane_pid}"`)
|
||||||
|
if (response.lines.length === 0) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return parseInt(response.lines[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async terminate (id: string): Promise<void> {
|
async terminate (id: string): Promise<void> {
|
||||||
await this.process.command(`kill-session -t ${id}`)
|
this.process.command(`kill-session -t ${id}`).catch(() => {
|
||||||
|
console.debug('Session already killed')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TMuxPersistenceProvider extends SessionPersistenceProvider {
|
export class TMuxPersistenceProvider extends SessionPersistenceProvider {
|
||||||
|
id = 'tmux'
|
||||||
|
displayName = 'Tmux'
|
||||||
private tmux: TMux
|
private tmux: TMux
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
super()
|
super()
|
||||||
|
if (this.isAvailable()) {
|
||||||
|
this.tmux = new TMux()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isAvailable (): boolean {
|
isAvailable (): boolean {
|
||||||
@@ -178,18 +195,20 @@ export class TMuxPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init () {
|
|
||||||
this.tmux = new TMux()
|
|
||||||
}
|
|
||||||
|
|
||||||
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||||
let sessions = await this.tmux.list()
|
let sessions = await this.tmux.list()
|
||||||
if (!sessions.includes(recoveryId)) {
|
if (!sessions.includes(recoveryId)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
let truePID$ = new AsyncSubject<number>()
|
||||||
|
this.tmux.getPID(recoveryId).then(pid => {
|
||||||
|
truePID$.next(pid)
|
||||||
|
truePID$.complete()
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
command: 'tmux',
|
command: 'tmux',
|
||||||
args: ['-L', 'terminus', 'attach-session', '-d', '-t', recoveryId],
|
args: ['-L', 'terminus', 'attach-session', '-d', '-t', recoveryId, ';', 'refresh-client'],
|
||||||
|
recoveredTruePID$: truePID$.asObservable(),
|
||||||
recoveryId,
|
recoveryId,
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,8 +3,8 @@ const psNode = require('ps-node')
|
|||||||
let nodePTY
|
let nodePTY
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { Logger, LogService, ElectronService } from 'terminus-core'
|
import { Logger, LogService, ElectronService, ConfigService } from 'terminus-core'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
|
|
||||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
@@ -178,18 +178,21 @@ export class SessionsService {
|
|||||||
private lastID = 0
|
private lastID = 0
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private persistence: SessionPersistenceProvider,
|
@Inject(SessionPersistenceProvider) private persistenceProviders: SessionPersistenceProvider[],
|
||||||
|
private config: ConfigService,
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
nodePTY = electron.remoteRequirePluginModule('terminus-terminal', 'node-pty', global as any)
|
nodePTY = electron.remoteRequirePluginModule('terminus-terminal', 'node-pty', global as any)
|
||||||
this.logger = log.create('sessions')
|
this.logger = log.create('sessions')
|
||||||
|
this.persistenceProviders = this.persistenceProviders.filter(x => x.isAvailable())
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepareNewSession (options: SessionOptions): Promise<SessionOptions> {
|
async prepareNewSession (options: SessionOptions): Promise<SessionOptions> {
|
||||||
if (this.persistence) {
|
let persistence = this.getPersistence()
|
||||||
let recoveryId = await this.persistence.startSession(options)
|
if (persistence) {
|
||||||
options = await this.persistence.attachSession(recoveryId)
|
let recoveryId = await persistence.startSession(options)
|
||||||
|
options = await persistence.attachSession(recoveryId)
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
@@ -198,10 +201,11 @@ export class SessionsService {
|
|||||||
this.lastID++
|
this.lastID++
|
||||||
options.name = `session-${this.lastID}`
|
options.name = `session-${this.lastID}`
|
||||||
let session = new Session(options)
|
let session = new Session(options)
|
||||||
|
let persistence = this.getPersistence()
|
||||||
session.destroyed$.first().subscribe(() => {
|
session.destroyed$.first().subscribe(() => {
|
||||||
delete this.sessions[session.name]
|
delete this.sessions[session.name]
|
||||||
if (this.persistence) {
|
if (persistence) {
|
||||||
this.persistence.terminateSession(session.recoveryId)
|
persistence.terminateSession(session.recoveryId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.sessions[session.name] = session
|
this.sessions[session.name] = session
|
||||||
@@ -209,9 +213,17 @@ export class SessionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async recover (recoveryId: string): Promise<SessionOptions> {
|
async recover (recoveryId: string): Promise<SessionOptions> {
|
||||||
if (!this.persistence) {
|
let persistence = this.getPersistence()
|
||||||
|
if (persistence) {
|
||||||
|
return await persistence.attachSession(recoveryId)
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return await this.persistence.attachSession(recoveryId)
|
|
||||||
|
private getPersistence (): SessionPersistenceProvider {
|
||||||
|
if (!this.config.store.terminal.persistence) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return this.persistenceProviders.find(x => x.id === this.config.store.terminal.persistence) || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
40
terminus-terminal/src/services/terminal.service.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { AppService, Logger, LogService } from 'terminus-core'
|
||||||
|
import { IShell } from '../api'
|
||||||
|
import { SessionsService } from './sessions.service'
|
||||||
|
import { TerminalTabComponent } from '../components/terminalTab.component'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TerminalService {
|
||||||
|
private logger: Logger
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private app: AppService,
|
||||||
|
private sessions: SessionsService,
|
||||||
|
log: LogService,
|
||||||
|
) {
|
||||||
|
this.logger = log.create('terminal')
|
||||||
|
}
|
||||||
|
|
||||||
|
async openTab (shell: IShell, cwd?: string): Promise<TerminalTabComponent> {
|
||||||
|
if (!cwd && this.app.activeTab instanceof TerminalTabComponent) {
|
||||||
|
cwd = await this.app.activeTab.session.getWorkingDirectory()
|
||||||
|
}
|
||||||
|
let env: any = Object.assign({}, process.env, shell.env || {})
|
||||||
|
|
||||||
|
this.logger.log(`Starting shell ${shell.name}`, shell)
|
||||||
|
let sessionOptions = await this.sessions.prepareNewSession({
|
||||||
|
command: shell.command,
|
||||||
|
args: shell.args || [],
|
||||||
|
cwd,
|
||||||
|
env,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.logger.log('Using session options:', sessionOptions)
|
||||||
|
|
||||||
|
return this.app.openNewTab(
|
||||||
|
TerminalTabComponent,
|
||||||
|
{ sessionOptions }
|
||||||
|
) as TerminalTabComponent
|
||||||
|
}
|
||||||
|
}
|
48
terminus-terminal/src/shells/cygwin32.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
let Registry = null
|
||||||
|
try {
|
||||||
|
Registry = require('winreg')
|
||||||
|
} catch (_) { } // tslint:disable-line no-empty
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Cygwin32ShellProvider extends ShellProvider {
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let cygwinPath = 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 || !item) {
|
||||||
|
return resolve(null)
|
||||||
|
}
|
||||||
|
resolve(item.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!cygwinPath) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
id: 'cygwin32',
|
||||||
|
name: 'Cygwin (32 bit)',
|
||||||
|
command: path.join(cygwinPath, 'bin', 'bash.exe'),
|
||||||
|
env: {
|
||||||
|
TERM: 'cygwin',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
48
terminus-terminal/src/shells/cygwin64.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
let Registry = null
|
||||||
|
try {
|
||||||
|
Registry = require('winreg')
|
||||||
|
} catch (_) { } // tslint:disable-line no-empty
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Cygwin64ShellProvider extends ShellProvider {
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let cygwinPath = await new Promise<string>(resolve => {
|
||||||
|
let reg = new Registry({ hive: Registry.HKLM, key: '\\Software\\Cygwin\\setup', arch: 'x64' })
|
||||||
|
reg.get('rootdir', (err, item) => {
|
||||||
|
if (err || !item) {
|
||||||
|
return resolve(null)
|
||||||
|
}
|
||||||
|
resolve(item.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!cygwinPath) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
id: 'cygwin64',
|
||||||
|
name: 'Cygwin',
|
||||||
|
command: path.join(cygwinPath, 'bin', 'bash.exe'),
|
||||||
|
env: {
|
||||||
|
TERM: 'cygwin',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
50
terminus-terminal/src/shells/gitBash.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
let Registry = null
|
||||||
|
try {
|
||||||
|
Registry = require('winreg')
|
||||||
|
} catch (_) { } // tslint:disable-line no-empty
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GitBashShellProvider extends ShellProvider {
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let gitBashPath = await new Promise<string>(resolve => {
|
||||||
|
let reg = new Registry({ hive: Registry.HKLM, key: '\\Software\\GitForWindows' })
|
||||||
|
reg.get('InstallPath', (err, item) => {
|
||||||
|
if (err || !item) {
|
||||||
|
resolve(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(item.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!gitBashPath) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
id: 'git-bash',
|
||||||
|
name: 'Git-Bash',
|
||||||
|
command: path.join(gitBashPath, 'bin', 'bash.exe'),
|
||||||
|
args: [ '--login', '-i' ],
|
||||||
|
env: {
|
||||||
|
TERM: 'cygwin',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
40
terminus-terminal/src/shells/linuxDefault.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import * as fs from 'mz/fs'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform, LogService, Logger } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LinuxDefaultShellProvider extends ShellProvider {
|
||||||
|
private logger: Logger
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
log: LogService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.logger = log.create('linuxDefaultShell')
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform !== Platform.Linux) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
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 [{
|
||||||
|
id: 'default',
|
||||||
|
name: 'User default',
|
||||||
|
command: '/bin/sh'
|
||||||
|
}]
|
||||||
|
} else {
|
||||||
|
return [{
|
||||||
|
id: 'default',
|
||||||
|
name: 'User default',
|
||||||
|
command: line.split(':')[6]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
terminus-terminal/src/shells/macDefault.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { exec } from 'mz/child_process'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MacOSDefaultShellProvider extends ShellProvider {
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform !== Platform.macOS) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let shellEntry = (await exec(`dscl . -read /Users/${process.env.LOGNAME} UserShell`))[0].toString()
|
||||||
|
return [{
|
||||||
|
id: 'default',
|
||||||
|
name: 'User default',
|
||||||
|
command: shellEntry.split(' ')[1].trim()
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
29
terminus-terminal/src/shells/posix.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as fs from 'mz/fs'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class POSIXShellsProvider extends ShellProvider {
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform === Platform.Windows) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return (await fs.readFile('/etc/shells', { encoding: 'utf-8' }))
|
||||||
|
.split('\n')
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(x => x && !x.startsWith('#'))
|
||||||
|
.map(x => ({
|
||||||
|
id: x,
|
||||||
|
name: x,
|
||||||
|
command: x,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
40
terminus-terminal/src/shells/windowsStock.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import * as path from 'path'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform, ElectronService } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WindowsStockShellsProvider extends ShellProvider {
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
private electron: ElectronService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'clink',
|
||||||
|
name: 'CMD (clink)',
|
||||||
|
command: 'cmd.exe',
|
||||||
|
args: [
|
||||||
|
'/k',
|
||||||
|
path.join(
|
||||||
|
path.dirname(this.electron.app.getPath('exe')),
|
||||||
|
'resources',
|
||||||
|
'clink',
|
||||||
|
`clink_${process.arch}.exe`,
|
||||||
|
),
|
||||||
|
'inject',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ id: 'cmd', name: 'CMD (stock)', command: 'cmd.exe' },
|
||||||
|
{ id: 'powershell', name: 'PowerShell', command: 'powershell.exe' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
31
terminus-terminal/src/shells/wsl.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import * as fs from 'mz/fs'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { HostAppService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
|
import { ShellProvider, IShell } from '../api'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WSLShellProvider extends ShellProvider {
|
||||||
|
constructor (
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide (): Promise<IShell[]> {
|
||||||
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const wslPath = `${process.env.windir}\\system32\\bash.exe`
|
||||||
|
if (!await fs.exists(wslPath)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{
|
||||||
|
id: 'wsl',
|
||||||
|
name: 'Bash on Windows',
|
||||||
|
command: wslPath
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|