Compare commits

..

No commits in common. "canary" and "v4.0.0-canary.5" have entirely different histories.

129 changed files with 5078 additions and 155415 deletions

View file

@ -3,9 +3,7 @@
"react",
"prettier",
"@typescript-eslint",
"eslint-comments",
"lodash",
"import"
"eslint-comments"
],
"extends": [
"eslint:recommended",
@ -34,11 +32,7 @@
"settings": {
"react": {
"version": "detect"
},
"import/resolver": {
"typescript": {}
},
"import/internal-regex": "^(electron|react)$"
}
},
"rules": {
"func-names": [
@ -99,30 +93,10 @@
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/consistent-type-imports": [ "error", { "disallowTypeAnnotations": false } ],
"lodash/prop-shorthand": [ "error", "always" ],
"lodash/import-scope": [ "error", "method" ],
"lodash/collection-return": "error",
"lodash/collection-method-value": "error",
"import/no-extraneous-dependencies": "error",
"import/no-anonymous-default-export": "error",
"import/order": [
"@typescript-eslint/consistent-type-imports": [
"error",
{
"groups": [
"builtin",
"internal",
"external",
"parent",
"sibling",
"index"
],
"newlines-between": "always",
"alphabetize": {
"order": "asc",
"orderImportKind": "desc",
"caseInsensitive": true
}
"disallowTypeAnnotations": false
}
]
}

View file

@ -1,137 +0,0 @@
name: 'Build Linux ARM'
description: 'Cross-compiles Hyper app for ARMv7l and ARM64 using arm-runner'
inputs:
node-version:
description: 'Node.js version to use'
required: true
matrix-name:
description: 'Matrix name (arch)'
required: true
matrix-cpu:
description: 'CPU architecture'
required: true
matrix-image:
description: 'Base OS image for ARM emulation'
required: true
upload-artifact:
description: 'Whether to upload artifacts'
required: false
default: 'true'
runs:
using: 'composite'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Fix node-gyp and Python
shell: bash
run: |
python3 -m pip install packaging setuptools
- name: Get yarn cache directory path
shell: bash
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache/restore@v4
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Delete old electron headers
# These can sometimes be present on a freshly-provisioned github runner, so remove them
shell: bash
run: |
rm -rf ~/.electron-gyp || true
- name: Install dependencies and tools
shell: bash
run: |
yarn install
sudo apt update
sudo apt install libarchive-tools
- name: Rebuild node-pty and all native modules for ARM
uses: pguyot/arm-runner-action@v2.6.5
with:
image_additional_mb: 2000
base_image: ${{ inputs.matrix-image }}
cpu: ${{ inputs.matrix-cpu }}
shell: bash
copy_artifact_path: target
copy_artifact_dest: target
commands: |
# Install Python distutils in the ARM container
sudo apt-get update
sudo apt-get install -y python3-pip python3-setuptools python3-dev
pip3 install setuptools --break-system-packages || pip3 install setuptools
cd target
# TODO upgrade node to 20.11.0 to match NODE_VERSION
wget https://nodejs.org/dist/v18.16.0/node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
tar -xJf node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
sudo cp node-v18.16.0-linux-${{ inputs.matrix-name }}/* /usr/local/ -R
rm node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
cd ..
npm run rebuild-node-pty
cd target
npx electron-rebuild
- name: Compile
shell: bash
run: yarn run build
- name: Chown rebuilt node_modules
shell: bash
run: |
sudo chown -R $USER:$USER target/node_modules
- name: Prepare v8 snapshot (only armv7l)
if: ${{ inputs.matrix-name == 'armv7l' }}
shell: bash
run: |
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install -y libglib2.0-0:i386 libexpat1:i386 libgcc-s1:i386
npm_config_arch=armv7l yarn run v8-snapshot:arch
- name: Build final Electron App for ARM
shell: bash
run: |
yarn run electron-builder -p never -l deb rpm AppImage pacman --${{ inputs.matrix-name }} -c electron-builder-linux-ci.json
env:
GH_TOKEN: ${{ env.GH_TOKEN }}
- name: Archive Build Artifacts
if: ${{ inputs.upload-artifact == 'true' }}
uses: actions/upload-artifact@v4
with:
name: hyper-linux-${{ inputs.matrix-name }}
path: |
dist/*.snap
dist/*.AppImage
dist/*.deb
dist/*.rpm
dist/*.pacman
# - name: Run E2E Tests on Linux
# if: runner.os == 'Linux'
# uses: GabrielBB/xvfb-action@v1.7
# with:
# run: |
# yarn run test
#

View file

@ -1,172 +0,0 @@
name: 'Build'
description: 'Builds, tests, and packages the app for release'
inputs:
node-version:
description: 'Node.js version to use'
required: true
matrix-name:
description: 'Matrix Name'
required: true
matrix-os:
description: 'Matrix OS'
required: true
upload-artifact:
description: 'Whether to upload artifacts'
required: false
default: 'true'
runs:
using: 'composite'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Fix node-gyp and Python
shell: bash
run: |
if [[ "$RUNNER_OS" == "macOS" ]]; then
brew install python-setuptools python-packaging
else
python3 -m pip install $EXTRA_ARGS packaging setuptools
fi
- name: Get yarn cache directory path
shell: bash
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache/restore@v4
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Delete old electron headers
# These can sometimes be present on a freshly-provisioned github runner, so remove them
shell: bash
run: |
rm -rf ~/.electron-gyp || true
- name: Install
shell: bash
run: yarn install
env:
npm_config_node_gyp: ${{ github.workspace }}${{ runner.os == 'Windows' && '\node_modules\node-gyp\bin\node-gyp.js' || '/node_modules/node-gyp/bin/node-gyp.js' }}
- name: Install libarchive-tools
shell: bash
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install libarchive-tools
- name: Rebuild native modules for Electron
shell: bash
run: npx electron-rebuild
- name: Lint and Run Unit Tests
shell: bash
run: yarn run test
- name: Getting Build Icon
shell: bash
if: github.ref == 'refs/heads/canary' || github.base_ref == 'canary'
run: |
cp build/canary.ico build/icon.ico
cp build/canary.icns build/icon.icns
- name: Build
shell: bash
run: |
if [ -z "$CSC_LINK" ] ; then unset CSC_LINK ; fi
if [ -z "$CSC_KEY_PASSWORD" ] ; then unset CSC_KEY_PASSWORD ; fi
if [ -z "$WIN_CSC_LINK" ] ; then unset WIN_CSC_LINK ; fi
if [ -z "$WIN_CSC_KEY_PASSWORD" ] ; then unset WIN_CSC_KEY_PASSWORD ; fi
if [ -z "$APPLE_ID" ] ; then unset APPLE_ID ; fi
if [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] ; then unset APPLE_APP_SPECIFIC_PASSWORD ; fi
yarn run dist
env:
GH_TOKEN: ${{ env.GH_TOKEN }}
CSC_LINK: ${{ env.MAC_CERT_P12_BASE64 }}
CSC_KEY_PASSWORD: ${{ env.MAC_CERT_P12_PASSWORD }}
WIN_CSC_LINK: ${{ env.WIN_CERT_P12_BASE64 }}
WIN_CSC_KEY_PASSWORD: ${{ env.WIN_CERT_P12_PASSWORD }}
APPLE_ID: ${{ env.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ env.APPLE_PASSWORD }}
- name: Archive Build Artifacts
uses: actions/upload-artifact@v4
with:
name: hyper-${{ runner.os }}-${{ inputs.matrix-name }}
path: |
dist/*.dmg
dist/*.snap
dist/*.AppImage
dist/*.deb
dist/*.pacman
dist/*.exe
- name: Run E2E Tests
shell: bash
if: runner.os != 'Linux'
run: yarn run test:e2e --verbose
- name: Run E2E Tests on Linux
if: runner.os == 'Linux'
uses: GabrielBB/xvfb-action@v1.7
with:
run: |
yarn run test:e2e
env:
SHELL: /bin/bash
DEBUG: "pw:browser*"
- name: Archive E2E test screenshot
uses: actions/upload-artifact@v4
with:
name: e2e-${{ inputs.matrix-os }}-${{ strategy.job-index }}
path: dist/tmp/*.png
- name: Save the pr number in an artifact
shell: bash
if: github.event_name == 'pull_request'
env:
PR_NUM: ${{ github.event.number }}
run: echo $PR_NUM > pr_num.txt
- name: Upload the pr num
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request'
with:
name: pr_num
path: ./pr_num.txt
- uses: actions/cache/save@v4
if: github.event_name == 'push'
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
# - name: Run E2E Tests (non-Linux)
# if: runner.os != 'Linux'
# shell: bash
# run: yarn run test:e2e --verbose
#
# - name: Run E2E Tests on Linux
# if: runner.os == 'Linux'
# uses: GabrielBB/xvfb-action@v1.7
# with:
# run: |
# yarn run test
#

View file

@ -5,31 +5,17 @@ updates:
schedule:
interval: weekly
time: '11:00'
open-pull-requests-limit: 30
target-branch: canary
versioning-strategy: increase
commit-message:
prefix: "chore(deps-dev):"
groups:
minorAndPatch:
update-types:
- "minor"
- "patch"
open-pull-requests-limit: 100
- package-ecosystem: npm
directory: "/app"
schedule:
interval: weekly
time: '11:00'
open-pull-requests-limit: 30
target-branch: canary
versioning-strategy: increase
commit-message:
prefix: "chore(deps):"
groups:
minorAndPatch:
update-types:
- "minor"
- "patch"
open-pull-requests-limit: 100
- package-ecosystem: github-actions
directory: "/"
schedule:

View file

@ -1 +1,8 @@
<!-- Please check `Allow edits from maintainers`. Thanks! -->
<!-- Hi there! Thanks for submitting a PR! We're excited to see what you've got for us.
- To help whoever reviews your PR, it'd be extremely helpful for you to list whether your PR is ready to be merged,
If there's anything left to do and if there are any related PRs
- It'd also be extremely helpful to enable us to update your PR incase we need to rebase or what-not by checking `Allow edits from maintainers`
- If your PR changes some API, please make a PR for hyper website too: https://github.com/vercel/hyper-site.
Thanks, again! -->

View file

@ -1,154 +0,0 @@
name: Node CI
on:
push:
branches:
workflow_dispatch:
defaults:
run:
shell: bash
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: 20.11.0
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# CSC_LINK: ${{ secrets.MAC_CERT_P12_BASE64 }}
# CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_P12_PASSWORD }}
# WIN_CSC_LINK: ${{ secrets.WIN_CERT_P12_BASE64 }}
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_P12_PASSWORD }}
# APPLE_ID: ${{ secrets.APPLE_ID }}
# APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
jobs:
build-ubuntu:
name: Build Ubuntu
runs-on: ubuntu-latest
concurrency:
group: build-ubuntu-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build
uses: ./.github/actions/build
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-os: ubuntu-latest
matrix-name: ubuntu
upload-artifact: false
build-macos:
name: Build macOS
runs-on: macos-latest
concurrency:
group: build-macos-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build
uses: ./.github/actions/build
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-os: macos-latest
matrix-name: macos
upload-artifact: false
build-windows:
name: Build Windows
runs-on: windows-latest
concurrency:
group: build-win-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build
uses: ./.github/actions/build
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-os: windows-latest
matrix-name: win
upload-artifact: false
# ARM Linux:
build-linux-armv7l:
name: Build Linux ARMv7l
runs-on: ubuntu-latest
concurrency:
group: build-linux-armv7l-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU and binfmt
run: |
# Install qemu-user-static
sudo apt-get update
sudo apt-get install -y qemu-user-static binfmt-support python3-setuptools
# Install distutils for node-gyp
sudo apt-get install -y python3-distutils || sudo apt-get install -y python3-dev python-is-python3
# Download and install binfmt configurations
wget https://github.com/qemu/qemu/raw/master/scripts/qemu-binfmt-conf.sh
chmod +x qemu-binfmt-conf.sh
sudo ./qemu-binfmt-conf.sh --qemu-path /usr/bin --qemu-suffix -static --debian
# Import the binfmt configurations
sudo update-binfmts --import qemu-arm
sudo update-binfmts --import qemu-aarch64
# Verify they exist
ls -la /proc/sys/fs/binfmt_misc/qemu-arm* || true
- name: Build ARMv7l
uses: ./.github/actions/build-linux-arm
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-name: armv7l
matrix-cpu: cortex-a8
matrix-image: raspios_lite:latest
upload-artifact: false
build-linux-arm64:
name: Build Linux ARM64
runs-on: ubuntu-latest
concurrency:
group: build-linux-arm64-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build ARM64
uses: ./.github/actions/build-linux-arm
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-name: arm64
matrix-cpu: cortex-a53
matrix-image: raspios_lite_arm64:latest
upload-artifact: false

View file

@ -35,11 +35,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -50,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@ -64,4 +64,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2

View file

@ -14,14 +14,14 @@ jobs:
WORKFLOW_RUN_INFO: ${{ toJSON(github.event.workflow_run) }}
run: echo "$WORKFLOW_RUN_INFO"
- name: Download Artifacts
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@v2.27.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: nodejs.yml
run_id: ${{ github.event.workflow_run.id }}
name: e2e
- name: Get PR number
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@v2.27.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: nodejs.yml
@ -29,7 +29,7 @@ jobs:
name: pr_num
- name: Read the pr_num file
id: pr_num_reader
uses: juliangruber/read-file-action@v1.1.7
uses: juliangruber/read-file-action@v1.1.6
with:
path: ./pr_num.txt
- name: List images

180
.github/workflows/nodejs.yml vendored Normal file
View file

@ -0,0 +1,180 @@
name: Node CI
on:
push:
branches:
- master
- canary
pull_request:
defaults:
run:
shell: bash
env:
NODE_VERSION: 18.x
jobs:
build:
runs-on: ${{matrix.os}}
strategy:
matrix:
os:
- macos-latest
- ubuntu-latest
- windows-latest
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache/restore@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: yarn install
env:
npm_config_node_gyp: ${{ github.workspace }}${{ runner.os == 'Windows' && '\node_modules\node-gyp\bin\node-gyp.js' || '/node_modules/node-gyp/bin/node-gyp.js' }}
- name: Install libarchive-tools
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install libarchive-tools
- name: Lint and Run Unit Tests
run: yarn run test
- name: Getting Build Icon
if: github.ref == 'refs/heads/canary' || github.base_ref == 'canary'
run: |
cp build/canary.ico build/icon.ico
cp build/canary.icns build/icon.icns
- name: Build
run: |
if [ -z "$CSC_LINK" ] ; then unset CSC_LINK ; fi
if [ -z "$CSC_KEY_PASSWORD" ] ; then unset CSC_KEY_PASSWORD ; fi
if [ -z "$WIN_CSC_LINK" ] ; then unset WIN_CSC_LINK ; fi
if [ -z "$WIN_CSC_KEY_PASSWORD" ] ; then unset WIN_CSC_KEY_PASSWORD ; fi
if [ -z "$APPLE_ID" ] ; then unset APPLE_ID ; fi
if [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] ; then unset APPLE_APP_SPECIFIC_PASSWORD ; fi
yarn run dist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.MAC_CERT_P12_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_P12_PASSWORD }}
WIN_CSC_LINK: ${{ secrets.WIN_CERT_P12_BASE64 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_P12_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
- name: Archive Build Artifacts
uses: LabhanshAgrawal/upload-artifact@v3
with:
path: |
dist/*.dmg
dist/*.snap
dist/*.AppImage
dist/*.deb
dist/*.rpm
dist/*.pacman
dist/*.exe
- name: Run E2E Tests
if: runner.os != 'Linux'
run: yarn run test:e2e
- name: Archive E2E test screenshot
if: runner.os != 'Linux'
uses: actions/upload-artifact@v3
with:
name: e2e
path: dist/tmp/*.png
- name: Save the pr number in an artifact
if: github.event_name == 'pull_request'
env:
PR_NUM: ${{ github.event.number }}
run: echo $PR_NUM > pr_num.txt
- name: Upload the pr num
uses: actions/upload-artifact@v3
if: github.event_name == 'pull_request'
with:
name: pr_num
path: ./pr_num.txt
- uses: actions/cache/save@v3
if: github.event_name == 'push'
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
build-linux-arm:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: armv7l
cpu: cortex-a8
image: raspios_lite:latest
- name: arm64
cpu: cortex-a53
image: raspios_lite_arm64:latest
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache/restore@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: |
yarn install
sudo apt update
sudo apt install libarchive-tools
- name: Compile
run: yarn run build
- name: rebuild node-pty
uses: pguyot/arm-runner-action@v2.5.2
with:
image_additional_mb: 2000
base_image: ${{ matrix.image }}
cpu: ${{ matrix.cpu }}
shell: bash
copy_artifact_path: target/node_modules/node-pty
copy_artifact_dest: target/node_modules
commands: |
wget https://nodejs.org/dist/v18.16.0/node-v18.16.0-linux-${{ matrix.name }}.tar.xz
tar -xJf node-v18.16.0-linux-${{ matrix.name }}.tar.xz
sudo cp node-v18.16.0-linux-${{ matrix.name }}/* /usr/local/ -R
npm run rebuild-node-pty
- name: chown node-pty
run: |
sudo chown -R $USER:$USER target/node_modules/node-pty
- name: Prepare v8 snapshot
if: matrix.name == 'armv7l'
run: |
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install -y libglib2.0-0:i386 libexpat1:i386 libgcc-s1:i386
npm_config_arch=armv7l yarn run v8-snapshot:arch
- name: Build
run: yarn run electron-builder -l deb rpm AppImage pacman --${{ matrix.name }} -c electron-builder-linux-ci.json
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Archive Build Artifacts
uses: LabhanshAgrawal/upload-artifact@v3
with:
path: |
dist/*.snap
dist/*.AppImage
dist/*.deb
dist/*.rpm
dist/*.pacman

View file

@ -1,194 +0,0 @@
name: Create Release
on:
push:
tags:
- "v*"
workflow_dispatch:
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: 20.11.0
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.MAC_CERT_P12_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_P12_PASSWORD }}
WIN_CSC_LINK: ${{ secrets.WIN_CERT_P12_BASE64 }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_P12_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
jobs:
build-ubuntu:
name: Build Ubuntu
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build
uses: ./.github/actions/build
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-os: ubuntu-latest
matrix-name: ubuntu
upload-artifact: true
build-macos:
name: Build macOS
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build
uses: ./.github/actions/build
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-os: macos-latest
matrix-name: macos
upload-artifact: true
build-windows:
name: Build Windows
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build
uses: ./.github/actions/build
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-os: windows-latest
matrix-name: win
upload-artifact: true
# ARM Linux:
build-linux-armv7l:
name: Build Linux ARMv7l
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU and binfmt
run: |
# Install qemu-user-static
sudo apt-get update
sudo apt-get install -y qemu-user-static binfmt-support python3-setuptools
# Install distutils for node-gyp
sudo apt-get install -y python3-distutils || sudo apt-get install -y python3-dev python-is-python3
# Download and install binfmt configurations
wget https://github.com/qemu/qemu/raw/master/scripts/qemu-binfmt-conf.sh
chmod +x qemu-binfmt-conf.sh
sudo ./qemu-binfmt-conf.sh --qemu-path /usr/bin --qemu-suffix -static --debian
# Import the binfmt configurations
sudo update-binfmts --import qemu-arm
sudo update-binfmts --import qemu-aarch64
# Verify they exist
ls -la /proc/sys/fs/binfmt_misc/qemu-arm* || true
- name: Build ARMv7l
uses: ./.github/actions/build-linux-arm
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-name: armv7l
matrix-cpu: cortex-a8
matrix-image: raspios_lite:latest
upload-artifact: true
build-linux-arm64:
name: Build Linux ARM64
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build ARM64
uses: ./.github/actions/build-linux-arm
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
node-version: ${{ env.NODE_VERSION }}
matrix-name: arm64
matrix-cpu: cortex-a53
matrix-image: raspios_lite_arm64:latest
upload-artifact: true
###
upload-release:
name: Upload and Create Release
needs:
- build-ubuntu
- build-macos
- build-windows
- build-linux-armv7l
- build-linux-arm64
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: List downloaded artifacts
run: ls -R ./artifacts
- name: Ensure non-empty artifacts
run: |
echo "Checking for zero-byte files in artifacts/..."
find artifacts/ -type f -empty -print -delete
echo "Cleaned up any zero-byte files."
echo "Remaining files:"
ls -R artifacts/
- name: Dump release assets for debugging
if: always()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub CLI needs this
run: |
echo "Fetching release assets for tag: ${{ github.ref_name }}"
gh release view "${{ github.ref_name }}" --repo "${{ github.repository }}" --json assets --jq '.assets[] | "\(.name) - \(.url)"' || true
- name: Create GitHub Release
# Pinned to work around https://github.com/softprops/action-gh-release/issues/445
uses: softprops/action-gh-release@v2.0.5
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
ACTIONS_STEP_DEBUG: true
with:
preserve_order: true
overwrite: true
name: Release ${{ github.ref_name }}
tag_name: ${{ github.ref }}
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
files: |
artifacts/**/*.dmg
artifacts/**/*.snap
artifacts/**/*.AppImage
artifacts/**/*.deb
artifacts/**/*.pacman
artifacts/**/*.exe

3
.husky/pre-push Normal file → Executable file
View file

@ -1 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn test

View file

@ -1 +0,0 @@
20.11.0

1
.nvmrc
View file

@ -1 +0,0 @@
20.11.0

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
yarnPath: .yarn/releases/yarn-classic.cjs

View file

@ -1,24 +1,19 @@
![](https://assets.vercel.com/image/upload/v1549723846/repositories/hyper/hyper-3-repo-banner.png)
<p align="center">
<img alt="hyper - modern web-based terminal" height=150 src="https://github.com/user-attachments/assets/3096f20a-8116-45ce-8c5e-0f1106107484">
</p>
<p>
<a aria-label="Vercel logo" href="https://vercel.com"><img
src="https://img.shields.io/badge/MADE%20BY%20Vercel-000000.svg?style=for-the-badge&logo=vercel&labelColor=000000&logoWidth=20"
/></a> <a aria-label="Quine logo" href="https://quineglobal.com"><img
width="143" height="28" alt="forked-by-quine" src="https://github.com/user-attachments/assets/57decaa2-7d8c-4d13-ada7-ff6b964346f7"
/></a>
</p>
[![Node CI](https://github.com/quine-global/hyper/actions/workflows/ci.yml/badge.svg?branch=canary)](https://github.com/quine-global/hyper/actions/workflows/ci.yml)
<a aria-label="Vercel logo" href="https://vercel.com">
<img src="https://img.shields.io/badge/MADE%20BY%20Vercel-000000.svg?style=for-the-badge&logo=vercel&labelColor=000000&logoWidth=20">
</a>
</p>
[![Node CI](https://github.com/vercel/hyper/workflows/Node%20CI/badge.svg?event=push)](https://github.com/vercel/hyper/actions?query=workflow%3A%22Node+CI%22+branch%3Acanary+event%3Apush)
[![Changelog #213](https://img.shields.io/badge/changelog-%23213-lightgrey.svg)](https://changelog.com/213)
For more details, head to: https://hyper.is
## Project goals
The goal of the project is to create a beautiful and customizable experience for command-line interface users, built on open web standards. We have picked up where Vercel left off, and intend to first offer stability, followed by a more tailored experience. We will still support customizability, but stability of theming APIs is not a goal.
The goal of the project is to create a beautiful and extensible experience for command-line interface users, built on open web standards. In the beginning, our focus will be primarily around speed, stability and the development of the correct API for extension authors.
In the future, we anticipate the community will come up with innovative additions to enhance what could be the simplest, most powerful and well-tested interface for productivity.

View file

@ -1,6 +1,5 @@
import {EventEmitter} from 'events';
import fetch from 'electron-fetch';
import {EventEmitter} from 'events';
class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
updateURL!: string;
@ -47,6 +46,4 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
}
}
const autoUpdaterLinux = new AutoUpdater();
export default autoUpdaterLinux;
export default new AutoUpdater();

View file

@ -1,6 +1,5 @@
import {app, Menu} from 'electron';
import type {BrowserWindow} from 'electron';
import {app, Menu} from 'electron';
import {openConfig, getConfig} from './config';
import {updatePlugins} from './plugins';
import {installCLI} from './utils/cli-install';

View file

@ -1,14 +1,11 @@
import {app} from 'electron';
import chokidar from 'chokidar';
import type {parsedConfig, configOptions} from '../typings/config';
import notify from './notify';
import {_import, getDefaultConfig} from './config/import';
import _openConfig from './config/open';
import {cfgPath, cfgDir} from './config/paths';
import notify from './notify';
import {getColorMap} from './utils/colors';
import type {parsedConfig, configOptions} from '../lib/config';
import {app} from 'electron';
const watchers: Function[] = [];
let cfg: parsedConfig = {} as any;

View file

@ -1,11 +1,9 @@
import {readFileSync, mkdirpSync} from 'fs-extra';
import type {rawConfig} from '../../typings/config';
import notify from '../notify';
import {_init} from './init';
import {migrateHyper3Config} from './migrate';
import {defaultCfg, cfgPath, plugs, defaultPlatformKeyPath} from './paths';
import {_init} from './init';
import notify from '../notify';
import type {rawConfig} from '../../lib/config';
import {migrateHyper3Config} from './migrate';
let defaultConfig: rawConfig;

View file

@ -1,10 +1,8 @@
import vm from 'vm';
import merge from 'lodash/merge';
import type {parsedConfig, rawConfig, configOptions} from '../../typings/config';
import notify from '../notify';
import mapKeys from '../utils/map-keys';
import type {parsedConfig, rawConfig, configOptions} from '../../lib/config';
import _ from 'lodash';
const _extract = (script?: vm.Script): Record<string, any> => {
const module: Record<string, any> = {};
@ -21,7 +19,7 @@ const _syntaxValidation = (cfg: string) => {
return new vm.Script(cfg, {filename: '.hyper.js'});
} catch (_err) {
const err = _err as {name: string};
notify(`Error loading config: ${err.name}`, JSON.stringify(err), {error: err});
notify(`Error loading config: ${err.name}`, `${err}`, {error: err});
}
};
@ -46,7 +44,7 @@ const _init = (userCfg: rawConfig, defaultCfg: rawConfig): parsedConfig => {
if (!conf.profiles.map((p) => p.name).includes(conf.defaultProfile)) {
conf.defaultProfile = conf.profiles[0].name;
}
return merge({}, defaultCfg.config, conf);
return _.merge({}, defaultCfg.config, conf);
} else {
notify('Error reading configuration: `config` key is missing');
return defaultCfg.config || ({} as configOptions);
@ -55,8 +53,8 @@ const _init = (userCfg: rawConfig, defaultCfg: rawConfig): parsedConfig => {
// Merging platform specific keymaps with user defined keymaps
keymaps: mapKeys({...defaultCfg.keymaps, ...userCfg?.keymaps}),
// Ignore undefined values in plugin and localPlugins array Issue #1862
plugins: userCfg?.plugins?.filter(Boolean) || [],
localPlugins: userCfg?.localPlugins?.filter(Boolean) || []
plugins: (userCfg?.plugins && userCfg.plugins.filter(Boolean)) || [],
localPlugins: (userCfg?.localPlugins && userCfg.localPlugins.filter(Boolean)) || []
};
};

View file

@ -1,16 +1,14 @@
import {dirname, resolve} from 'path';
import {builders, namedTypes} from 'ast-types';
import type {ExpressionKind} from 'ast-types/lib/gen/kinds';
import {copy, copySync, existsSync, readFileSync, writeFileSync} from 'fs-extra';
import merge from 'lodash/merge';
import {parse, prettyPrint} from 'recast';
import {builders, namedTypes} from 'ast-types';
import * as babelParser from 'recast/parsers/babel';
import {copy, copySync, existsSync, readFileSync, writeFileSync} from 'fs-extra';
import {dirname, resolve} from 'path';
import _ from 'lodash';
import notify from '../notify';
import {_extractDefault} from './init';
import {cfgDir, cfgPath, defaultCfg, legacyCfgPath, plugs, schemaFile, schemaPath} from './paths';
import type {ExpressionKind} from 'ast-types/lib/gen/kinds';
// function to remove all json serializable entries from an array expression
function removeElements(node: namedTypes.ArrayExpression): namedTypes.ArrayExpression {
@ -168,7 +166,7 @@ export const migrateHyper3Config = () => {
try {
const legacyCfgRaw = readFileSync(legacyCfgPath, 'utf8');
const legacyCfgData = _extractDefault(legacyCfgRaw);
newCfgData = merge({}, defaultCfgData, legacyCfgData);
newCfgData = _.merge({}, defaultCfgData, legacyCfgData);
const pluginCode = configToPlugin(legacyCfgRaw);
if (pluginCode) {

View file

@ -1,10 +1,7 @@
import {exec} from 'child_process';
import {shell} from 'electron';
import * as Registry from 'native-reg';
import {cfgPath} from './paths';
import * as Registry from 'native-reg';
import {exec} from 'child_process';
const getUserChoiceKey = () => {
try {
@ -60,7 +57,7 @@ const openNotepad = (file: string) =>
});
});
const openConfig = () => {
export default () => {
// Windows opens .js files with WScript.exe by default
// If the user hasn't set up an editor for .js files, we fallback to notepad.
if (process.platform === 'win32') {
@ -76,5 +73,3 @@ const openConfig = () => {
}
return shell.openPath(cfgPath).then((error) => error === '');
};
export default openConfig;

View file

@ -1,10 +1,8 @@
// This module exports paths, names, and other metadata that is referenced
import {statSync} from 'fs';
import {homedir} from 'os';
import {resolve, join} from 'path';
import {app} from 'electron';
import {statSync} from 'fs';
import {resolve, join} from 'path';
import isDev from 'electron-is-dev';
const cfgFile = 'hyper.json';
@ -17,15 +15,15 @@ const homeDirectory = homedir();
let cfgDir = process.env.XDG_CONFIG_HOME
? join(process.env.XDG_CONFIG_HOME, 'Hyper')
: process.platform === 'win32'
? app.getPath('userData')
: join(homeDirectory, '.config', 'Hyper');
? app.getPath('userData')
: join(homeDirectory, '.config', 'Hyper');
const legacyCfgPath = join(
process.env.XDG_CONFIG_HOME !== undefined
? join(process.env.XDG_CONFIG_HOME, 'hyper')
: process.platform == 'win32'
? app.getPath('userData')
: homedir(),
? app.getPath('userData')
: homedir(),
'.hyper.js'
);

View file

@ -1,6 +1,5 @@
import type {BrowserWindow} from 'electron';
import Config from 'electron-store';
import type {BrowserWindow} from 'electron';
export const defaults = {
windowPosition: [50, 50] as [number, number],

View file

@ -1,7 +1,3 @@
declare module 'php-escape-shell' {
export function php_escapeshellcmd(path: string): string;
}
declare module 'git-describe' {
export function gitDescribe(...args: any[]): void;
}

View file

@ -1,10 +1,10 @@
import type {Server} from '../app/rpc';
import type {Server} from './rpc';
declare global {
namespace Electron {
interface App {
config: typeof import('../app/config');
plugins: typeof import('../app/plugins');
config: typeof import('./config');
plugins: typeof import('./plugins');
getWindows: () => Set<BrowserWindow>;
getLastFocusedWindow: () => BrowserWindow | null;
windowCallback?: (win: BrowserWindow) => void;

1
app/index.d.ts vendored Normal file
View file

@ -0,0 +1 @@
// Dummy file, required by tsc

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line import/order
import {cfgPath} from './config/paths';
// Print diagnostic information for a few arguments instead of running Hyper.
@ -12,30 +11,27 @@ if (['--help', '-v', '--version'].includes(process.argv[1])) {
}
// Enable remote module
// eslint-disable-next-line import/order
import {initialize as remoteInitialize} from '@electron/remote/main';
remoteInitialize();
// set up config
// eslint-disable-next-line import/order
import * as config from './config';
config.setup();
// Native
import {resolve} from 'path';
// Packages
import {app, BrowserWindow, Menu, screen} from 'electron';
import isDev from 'electron-is-dev';
import {gitDescribe} from 'git-describe';
import parseUrl from 'parse-url';
import isDev from 'electron-is-dev';
import * as config from './config';
// set up config
config.setup();
import * as AppMenu from './menus/menu';
import * as plugins from './plugins';
import {newWindow} from './ui/window';
import {installCLI} from './utils/cli-install';
import * as AppMenu from './menus/menu';
import {newWindow} from './ui/window';
import * as windowUtils from './utils/window-utils';
import parseUrl from 'parse-url';
const windowSet = new Set<BrowserWindow>([]);
@ -143,10 +139,6 @@ app.on('ready', () =>
windowSet.add(hwin);
void hwin.loadURL(url);
hwin.once('ready-to-show', () => {
hwin.show();
});
// the window can be closed by the browser process itself
hwin.on('close', () => {
hwin.clean();
@ -190,7 +182,7 @@ app.on('ready', () =>
}
}
]);
app.dock?.setMenu(dockMenu);
app.dock.setMenu(dockMenu);
}
Menu.setApplicationMenu(AppMenu.buildMenu(menu));

View file

@ -1,21 +1,20 @@
// Packages
import {app, dialog, Menu} from 'electron';
import type {BrowserWindow} from 'electron';
import {app, dialog, Menu} from 'electron';
// Utilities
import {execCommand} from '../commands';
import {getConfig} from '../config';
import {icon} from '../config/paths';
import {getDecoratedKeymaps} from '../plugins';
import {getRendererTypes} from '../utils/renderer-utils';
import darwinMenu from './menus/darwin';
import editMenu from './menus/edit';
import helpMenu from './menus/help';
import shellMenu from './menus/shell';
import toolsMenu from './menus/tools';
import viewMenu from './menus/view';
import shellMenu from './menus/shell';
import editMenu from './menus/edit';
import toolsMenu from './menus/tools';
import windowMenu from './menus/window';
import helpMenu from './menus/help';
import darwinMenu from './menus/darwin';
import {getDecoratedKeymaps} from '../plugins';
import {execCommand} from '../commands';
import {getRendererTypes} from '../utils/renderer-utils';
const appName = app.name;
const appVersion = app.getVersion();
@ -56,19 +55,7 @@ export const createMenu = (
void dialog.showMessageBox({
title: `About ${appName}`,
message: `${appName} ${appVersion} (${updateChannel})`,
detail: `
Renderers: ${renderers}
Plugins: ${pluginList}
Maintained by QUINE Global
Copyright © 2025
Created by Guillermo Rauch
Copyright © 2022 Vercel, Inc.
`
.split('\n')
.map((z) => z.trim())
.join('\n'),
detail: `Renderers: ${renderers}\nPlugins: ${pluginList}\n\nCreated by Guillermo Rauch\nCopyright © 2022 Vercel, Inc.`,
buttons: [],
icon: icon as any
});
@ -77,7 +64,7 @@ export const createMenu = (
...(process.platform === 'darwin' ? [darwinMenu(commandKeys, execCommand, showAbout)] : []),
shellMenu(
commandKeys,
(command, focusedWindow) => execCommand(command, focusedWindow as BrowserWindow | undefined),
execCommand,
getConfig().profiles.map((p) => p.name)
),
editMenu(commandKeys, execCommand),

View file

@ -1,9 +1,9 @@
// This menu label is overrided by OSX to be the appName
// The label is set to appName here so it matches actual behavior
import {app} from 'electron';
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
import {app} from 'electron';
const darwinMenu = (
export default (
commandKeys: Record<string, string>,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void,
showAbout: () => void
@ -55,5 +55,3 @@ const darwinMenu = (
]
};
};
export default darwinMenu;

View file

@ -1,6 +1,6 @@
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
const editMenu = (
export default (
commandKeys: Record<string, string>,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
) => {
@ -38,7 +38,7 @@ const editMenu = (
label: 'Select All',
accelerator: commandKeys['editor:selectAll'],
click(item, focusedWindow) {
execCommand('editor:selectAll', focusedWindow as BrowserWindow | undefined);
execCommand('editor:selectAll', focusedWindow);
}
},
{
@ -51,28 +51,28 @@ const editMenu = (
label: 'Previous word',
accelerator: commandKeys['editor:movePreviousWord'],
click(item, focusedWindow) {
execCommand('editor:movePreviousWord', focusedWindow as BrowserWindow | undefined);
execCommand('editor:movePreviousWord', focusedWindow);
}
},
{
label: 'Next word',
accelerator: commandKeys['editor:moveNextWord'],
click(item, focusedWindow) {
execCommand('editor:moveNextWord', focusedWindow as BrowserWindow | undefined);
execCommand('editor:moveNextWord', focusedWindow);
}
},
{
label: 'Line beginning',
accelerator: commandKeys['editor:moveBeginningLine'],
click(item, focusedWindow) {
execCommand('editor:moveBeginningLine', focusedWindow as BrowserWindow | undefined);
execCommand('editor:moveBeginningLine', focusedWindow);
}
},
{
label: 'Line end',
accelerator: commandKeys['editor:moveEndLine'],
click(item, focusedWindow) {
execCommand('editor:moveEndLine', focusedWindow as BrowserWindow | undefined);
execCommand('editor:moveEndLine', focusedWindow);
}
}
]
@ -84,28 +84,28 @@ const editMenu = (
label: 'Previous word',
accelerator: commandKeys['editor:deletePreviousWord'],
click(item, focusedWindow) {
execCommand('editor:deletePreviousWord', focusedWindow as BrowserWindow | undefined);
execCommand('editor:deletePreviousWord', focusedWindow);
}
},
{
label: 'Next word',
accelerator: commandKeys['editor:deleteNextWord'],
click(item, focusedWindow) {
execCommand('editor:deleteNextWord', focusedWindow as BrowserWindow | undefined);
execCommand('editor:deleteNextWord', focusedWindow);
}
},
{
label: 'Line beginning',
accelerator: commandKeys['editor:deleteBeginningLine'],
click(item, focusedWindow) {
execCommand('editor:deleteBeginningLine', focusedWindow as BrowserWindow | undefined);
execCommand('editor:deleteBeginningLine', focusedWindow);
}
},
{
label: 'Line end',
accelerator: commandKeys['editor:deleteEndLine'],
click(item, focusedWindow) {
execCommand('editor:deleteEndLine', focusedWindow as BrowserWindow | undefined);
execCommand('editor:deleteEndLine', focusedWindow);
}
}
]
@ -117,14 +117,14 @@ const editMenu = (
label: 'Clear Buffer',
accelerator: commandKeys['editor:clearBuffer'],
click(item, focusedWindow) {
execCommand('editor:clearBuffer', focusedWindow as BrowserWindow | undefined);
execCommand('editor:clearBuffer', focusedWindow);
}
},
{
label: 'Search',
accelerator: commandKeys['editor:search'],
click(item, focusedWindow) {
execCommand('editor:search', focusedWindow as BrowserWindow | undefined);
execCommand('editor:search', focusedWindow);
}
}
];
@ -147,5 +147,3 @@ const editMenu = (
submenu
};
};
export default editMenu;

View file

@ -1,14 +1,11 @@
import {release} from 'os';
import {app, shell, dialog, clipboard} from 'electron';
import type {MenuItemConstructorOptions} from 'electron';
import {app, shell, dialog, clipboard} from 'electron';
import {getConfig, getPlugins} from '../../config';
const {arch, env, platform, versions} = process;
import {version} from '../../package.json';
const {arch, env, platform, versions} = process;
const helpMenu = (commands: Record<string, string>, showAbout: () => void): MenuItemConstructorOptions => {
export default (commands: Record<string, string>, showAbout: () => void): MenuItemConstructorOptions => {
const submenu: MenuItemConstructorOptions[] = [
{
label: `${app.name} Website`,
@ -61,11 +58,11 @@ ${JSON.stringify(getPlugins(), null, 2)}
\`\`\`
</details>`;
const issueURL = `https://github.com/quine-global/hyper/issues/new?body=${encodeURIComponent(body)}`;
const issueURL = `https://github.com/vercel/hyper/issues/new?body=${encodeURIComponent(body)}`;
const copyAndSend = () => {
clipboard.writeText(body);
void shell.openExternal(
`https://github.com/quine-global/hyper/issues/new?body=${encodeURIComponent(
`https://github.com/vercel/hyper/issues/new?body=${encodeURIComponent(
'<!-- We have written the needed data into your clipboard because it was too large to send. ' +
'Please paste. -->\n'
)}`
@ -110,5 +107,3 @@ ${JSON.stringify(getPlugins(), null, 2)}
submenu
};
};
export default helpMenu;

View file

@ -1,8 +1,8 @@
import type {BaseWindow, MenuItemConstructorOptions} from 'electron';
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
const shellMenu = (
export default (
commandKeys: Record<string, string>,
execCommand: (command: string, focusedWindow?: BaseWindow) => void,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void,
profiles: string[]
): MenuItemConstructorOptions => {
const isMac = process.platform === 'darwin';
@ -100,5 +100,3 @@ const shellMenu = (
]
};
};
export default shellMenu;

View file

@ -1,6 +1,6 @@
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
const toolsMenu = (
export default (
commands: Record<string, string>,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
): MenuItemConstructorOptions => {
@ -45,5 +45,3 @@ const toolsMenu = (
]
};
};
export default toolsMenu;

View file

@ -1,6 +1,6 @@
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
const viewMenu = (
export default (
commandKeys: Record<string, string>,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
): MenuItemConstructorOptions => {
@ -11,21 +11,21 @@ const viewMenu = (
label: 'Reload',
accelerator: commandKeys['window:reload'],
click(item, focusedWindow) {
execCommand('window:reload', focusedWindow as BrowserWindow);
execCommand('window:reload', focusedWindow);
}
},
{
label: 'Full Reload',
accelerator: commandKeys['window:reloadFull'],
click(item, focusedWindow) {
execCommand('window:reloadFull', focusedWindow as BrowserWindow);
execCommand('window:reloadFull', focusedWindow);
}
},
{
label: 'Developer Tools',
accelerator: commandKeys['window:devtools'],
click: (item, focusedWindow) => {
execCommand('window:devtools', focusedWindow as BrowserWindow);
execCommand('window:devtools', focusedWindow);
}
},
{
@ -35,25 +35,23 @@ const viewMenu = (
label: 'Reset Zoom Level',
accelerator: commandKeys['zoom:reset'],
click(item, focusedWindow) {
execCommand('zoom:reset', focusedWindow as BrowserWindow);
execCommand('zoom:reset', focusedWindow);
}
},
{
label: 'Zoom In',
accelerator: commandKeys['zoom:in'],
click(item, focusedWindow) {
execCommand('zoom:in', focusedWindow as BrowserWindow);
execCommand('zoom:in', focusedWindow);
}
},
{
label: 'Zoom Out',
accelerator: commandKeys['zoom:out'],
click(item, focusedWindow) {
execCommand('zoom:out', focusedWindow as BrowserWindow);
execCommand('zoom:out', focusedWindow);
}
}
]
};
};
export default viewMenu;

View file

@ -1,6 +1,6 @@
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
const windowMenu = (
export default (
commandKeys: Record<string, string>,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
): MenuItemConstructorOptions => {
@ -37,14 +37,14 @@ const windowMenu = (
label: 'Previous',
accelerator: commandKeys['tab:prev'],
click: (item, focusedWindow) => {
execCommand('tab:prev', focusedWindow as BrowserWindow);
execCommand('tab:prev', focusedWindow);
}
},
{
label: 'Next',
accelerator: commandKeys['tab:next'],
click: (item, focusedWindow) => {
execCommand('tab:next', focusedWindow as BrowserWindow);
execCommand('tab:next', focusedWindow);
}
},
{
@ -63,14 +63,14 @@ const windowMenu = (
label: 'Previous',
accelerator: commandKeys['pane:prev'],
click: (item, focusedWindow) => {
execCommand('pane:prev', focusedWindow as BrowserWindow);
execCommand('pane:prev', focusedWindow);
}
},
{
label: 'Next',
accelerator: commandKeys['pane:next'],
click: (item, focusedWindow) => {
execCommand('pane:next', focusedWindow as BrowserWindow);
execCommand('pane:next', focusedWindow);
}
}
]
@ -84,7 +84,7 @@ const windowMenu = (
{
label: 'Toggle Always on Top',
click: (item, focusedWindow) => {
execCommand('window:toggleKeepOnTop', focusedWindow as BrowserWindow);
execCommand('window:toggleKeepOnTop', focusedWindow);
}
},
{
@ -94,5 +94,3 @@ const windowMenu = (
]
};
};
export default windowMenu;

View file

@ -1,9 +1,7 @@
import type {BrowserWindow} from 'electron';
import fetch from 'electron-fetch';
import ms from 'ms';
import fetch from 'electron-fetch';
import {version} from './package.json';
import type {BrowserWindow} from 'electron';
const NEWS_URL = 'https://hyper-news.now.sh';

View file

@ -1,5 +1,4 @@
import {app, Notification} from 'electron';
import {icon} from './config/paths';
export default function notify(title: string, body = '', details: {error?: any} = {}) {

View file

@ -2,50 +2,44 @@
"name": "hyper",
"productName": "Hyper",
"description": "A terminal built on web technologies",
"version": "4.0.0-q-canary.8",
"version": "4.0.0-canary.5",
"license": "MIT",
"author": {
"name": "ZEIT, Inc.",
"email": "team@zeit.co"
},
"repository": "quine-global/hyper",
"repository": "zeit/hyper",
"scripts": {
"postinstall": "npx patch-package"
},
"dependencies": {
"@babel/parser": "7.27.0",
"@electron/remote": "2.1.2",
"ast-types": "^0.16.1",
"@babel/parser": "7.22.7",
"@electron/remote": "2.0.10",
"async-retry": "1.3.3",
"chokidar": "^3.6.0",
"chokidar": "^3.5.3",
"color": "4.2.3",
"default-shell": "1.0.1",
"electron-devtools-installer": "3.2.1",
"electron-fetch": "1.9.1",
"electron-is-dev": "2.0.0",
"electron-store": "8.2.0",
"fs-extra": "11.3.0",
"electron-store": "8.1.0",
"fs-extra": "11.1.1",
"git-describe": "4.1.1",
"lodash": "4.17.21",
"ms": "2.1.3",
"native-process-working-directory": "^1.0.2",
"node-pty": "1.1.0-beta33",
"node-pty": "1.0.0",
"os-locale": "5.0.0",
"parse-url": "9.2.0",
"parse-url": "8.1.0",
"queue": "6.0.2",
"quine-electron-drag-click": "2.0.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"recast": "0.23.11",
"semver": "7.7.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"recast": "0.23.2",
"semver": "7.5.4",
"shell-env": "3.0.1",
"sudo-prompt": "^9.2.1",
"uuid": "10.0.0"
"uuid": "9.0.0"
},
"optionalDependencies": {
"native-reg": "1.1.1"
},
"devDependencies": {
"node-gyp": "^10.2.0"
}
}

View file

@ -0,0 +1,15 @@
diff --git a/node_modules/node-pty/src/win/conpty.cc b/node_modules/node-pty/src/win/conpty.cc
index 47af75c..884d542 100644
--- a/node_modules/node-pty/src/win/conpty.cc
+++ b/node_modules/node-pty/src/win/conpty.cc
@@ -472,10 +472,6 @@ static NAN_METHOD(PtyKill) {
}
}
- DisconnectNamedPipe(handle->hIn);
- DisconnectNamedPipe(handle->hOut);
- CloseHandle(handle->hIn);
- CloseHandle(handle->hOut);
CloseHandle(handle->hShell);
}

View file

@ -1,28 +1,24 @@
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import {exec, execFile} from 'child_process';
import {writeFileSync} from 'fs';
import {resolve, basename} from 'path';
import {promisify} from 'util';
import {app, dialog, ipcMain as _ipcMain} from 'electron';
import type {BrowserWindow, App, MenuItemConstructorOptions} from 'electron';
import React from 'react';
import {app, dialog, ipcMain as _ipcMain} from 'electron';
import {resolve, basename} from 'path';
import {writeFileSync} from 'fs';
import Config from 'electron-store';
import ms from 'ms';
import React from 'react';
import ReactDom from 'react-dom';
import type {IpcMainWithCommands} from '../typings/common';
import type {configOptions} from '../typings/config';
import * as config from './config';
import {plugs} from './config/paths';
import notify from './notify';
import {availableExtensions} from './plugins/extensions';
import {install} from './plugins/install';
import {plugs} from './config/paths';
import mapKeys from './utils/map-keys';
import type {configOptions} from '../lib/config';
import {promisify} from 'util';
import {exec, execFile} from 'child_process';
import type {IpcMainWithCommands} from '../common';
// local storage
const cache = new Config();
@ -211,7 +207,7 @@ function syncPackageJSON() {
description: 'Auto-generated from `hyper.json`!',
private: true,
version: '0.0.1',
repository: 'quine-global/hyper',
repository: 'vercel/hyper',
license: 'MIT',
homepage: 'https://hyper.is',
dependencies
@ -471,10 +467,3 @@ ipcMain.handle('child_process.exec', (event, command, options) => {
ipcMain.handle('child_process.execFile', (event, file, args, options) => {
return promisify(execFile)(file, args, options);
});
ipcMain.handle('getLoadedPluginVersions', () => getLoadedPluginVersions());
ipcMain.handle('getPaths', () => getPaths());
ipcMain.handle('getBasePaths', () => getBasePaths());
ipcMain.handle('getDeprecatedConfig', () => getDeprecatedConfig());
ipcMain.handle('getDecoratedConfig', (e, profile) => getDecoratedConfig(profile));
ipcMain.handle('getDecoratedKeymaps', () => getDecoratedKeymaps());

View file

@ -1,8 +1,6 @@
import cp from 'child_process';
import ms from 'ms';
import queue from 'queue';
import ms from 'ms';
import {yarn, plugs} from '../config/paths';
export const install = (fn: (err: string | null) => void) => {

View file

@ -1,11 +1,8 @@
import {EventEmitter} from 'events';
import {ipcMain} from 'electron';
import type {BrowserWindow, IpcMainEvent} from 'electron';
import {ipcMain} from 'electron';
import {v4 as uuidv4} from 'uuid';
import type {TypedEmitter, MainEvents, RendererEvents, FilterNever} from '../typings/common';
import type {TypedEmitter, MainEvents, RendererEvents, FilterNever} from '../common';
export class Server {
emitter: TypedEmitter<MainEvents>;
@ -31,7 +28,7 @@ export class Server {
// to support reloading the window and re-initializing
// the channel
this.wc.on('did-finish-load', () => {
this.wc.send('init', uid, win.profileName);
this.wc.send('init', uid);
});
}
@ -76,8 +73,6 @@ export class Server {
}
}
const createRPC = (win: BrowserWindow) => {
export default (win: BrowserWindow) => {
return new Server(win);
};
export default createRPC;

View file

@ -1,17 +1,14 @@
import {EventEmitter} from 'events';
import {dirname} from 'path';
import {StringDecoder} from 'string_decoder';
import defaultShell from 'default-shell';
import type {IPty, IWindowsPtyForkOptions, spawn as npSpawn} from 'node-pty';
import osLocale from 'os-locale';
import shellEnv from 'shell-env';
import * as config from './config';
import {cliScriptPath} from './config/paths';
import {productName, version} from './package.json';
import {getDecoratedEnv} from './plugins';
import {getFallBackShellConfig} from './utils/shell-fallback';
import {productName, version} from './package.json';
import * as config from './config';
import type {IPty, IWindowsPtyForkOptions, spawn as npSpawn} from 'node-pty';
import {cliScriptPath} from './config/paths';
import {dirname} from 'path';
import shellEnv from 'shell-env';
import osLocale from 'os-locale';
const createNodePtyError = () =>
new Error(
@ -185,32 +182,15 @@ export default class Session extends EventEmitter {
// this will inform users in case there are errors in the config instead of instant exit
const runDuration = new Date().getTime() - this.initTimestamp;
if (e.exitCode > 0 && runDuration < 1000) {
const fallBackShellConfig = getFallBackShellConfig(shell, shellArgs, defaultShell, defaultShellArgs);
if (fallBackShellConfig) {
const msg = `
const defaultShellConfig = {shell: defaultShell, shellArgs: defaultShellArgs};
const msg = `
shell exited in ${runDuration} ms with exit code ${e.exitCode}
please check the shell config: ${JSON.stringify({shell, shellArgs}, undefined, 2)}
using fallback shell config: ${JSON.stringify(fallBackShellConfig, undefined, 2)}
fallback to default shell config: ${JSON.stringify(defaultShellConfig, undefined, 2)}
`;
console.warn(msg);
this.batcher?.write(msg.replace(/\n/g, '\r\n'));
this.init({
uid,
rows,
cols,
cwd,
shell: fallBackShellConfig.shell,
shellArgs: fallBackShellConfig.shellArgs,
profile
});
} else {
const msg = `
shell exited in ${runDuration} ms with exit code ${e.exitCode}
No fallback available, please check the shell config.
`;
console.warn(msg);
this.batcher?.write(msg.replace(/\n/g, '\r\n'));
}
console.warn(msg);
this.batcher?.write(msg.replace(/\n/g, '\r\n'));
this.init({uid, rows, cols, cwd, ...defaultShellConfig, profile});
} else {
this.ended = true;
this.emit('exit');

View file

@ -1,20 +1,12 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"declaration": true,
"declarationDir": "../dist/tmp/appdts/",
"outDir": "../target/",
"composite": true,
"noImplicitAny": false
},
"include": [
"./**/*",
"./package.json",
"../typings/extend-electron.d.ts",
"../typings/ext-modules.d.ts"
],
"exclude": [
"../dist/**/*",
"../target/**/*"
"./package.json"
]
}

View file

@ -1,11 +1,9 @@
import type {MenuItemConstructorOptions, BrowserWindow} from 'electron';
import {execCommand} from '../commands';
import {getProfiles} from '../config';
import editMenu from '../menus/menus/edit';
import shellMenu from '../menus/menus/shell';
import {execCommand} from '../commands';
import {getDecoratedKeymaps} from '../plugins';
import type {MenuItemConstructorOptions, BrowserWindow} from 'electron';
import {getProfiles} from '../config';
const separator: MenuItemConstructorOptions = {type: 'separator'};
const getCommandKeys = (keymaps: Record<string, string[]>): Record<string, string> =>
@ -23,14 +21,14 @@ const filterCutCopy = (selection: string, menuItem: MenuItemConstructorOptions)
return menuItem;
};
const contextMenuTemplate = (
export default (
createWindow: (fn?: (win: BrowserWindow) => void, options?: Record<string, any>) => BrowserWindow,
selection: string
) => {
const commandKeys = getCommandKeys(getDecoratedKeymaps());
const _shell = shellMenu(
commandKeys,
(command, focusedWindow) => execCommand(command, focusedWindow as BrowserWindow | undefined),
execCommand,
getProfiles().map((p) => p.name)
).submenu as MenuItemConstructorOptions[];
const _edit = editMenu(commandKeys, execCommand).submenu.filter(filterCutCopy.bind(null, selection));
@ -38,5 +36,3 @@ const contextMenuTemplate = (
.concat(separator, _shell)
.filter((menuItem) => !Object.prototype.hasOwnProperty.call(menuItem, 'enabled') || menuItem.enabled);
};
export default contextMenuTemplate;

View file

@ -1,35 +1,26 @@
import {existsSync} from 'fs';
import type {BrowserWindowConstructorOptions} from 'electron';
import {app, BrowserWindow, shell, Menu} from 'electron';
import {isAbsolute, normalize, sep} from 'path';
import {URL, fileURLToPath} from 'url';
import {app, BrowserWindow, shell, Menu} from 'electron';
import type {BrowserWindowConstructorOptions} from 'electron';
import {enable as remoteEnable} from '@electron/remote/main';
import isDev from 'electron-is-dev';
import {getWorkingDirectoryFromPID} from 'native-process-working-directory';
import electronDragClick from 'quine-electron-drag-click';
import {v4 as uuidv4} from 'uuid';
import type {sessionExtraOptions} from '../../typings/common';
import type {configOptions} from '../../typings/config';
import {execCommand} from '../commands';
import {getDefaultProfile} from '../config';
import {icon, homeDirectory} from '../config/paths';
import fetchNotifications from '../notifications';
import notify from '../notify';
import {decorateSessionOptions, decorateSessionClass} from '../plugins';
import createRPC from '../rpc';
import Session from '../session';
import isDev from 'electron-is-dev';
import updater from '../updater';
import {setRendererType, unsetRendererType} from '../utils/renderer-utils';
import toElectronBackgroundColor from '../utils/to-electron-background-color';
import {icon, homeDirectory} from '../config/paths';
import createRPC from '../rpc';
import notify from '../notify';
import fetchNotifications from '../notifications';
import Session from '../session';
import contextMenuTemplate from './contextmenu';
if (process.platform === 'darwin') {
electronDragClick();
}
import {execCommand} from '../commands';
import {setRendererType, unsetRendererType} from '../utils/renderer-utils';
import {decorateSessionOptions, decorateSessionClass} from '../plugins';
import {enable as remoteEnable} from '@electron/remote/main';
import type {configOptions} from '../../lib/config';
import {getWorkingDirectoryFromPID} from 'native-process-working-directory';
import {existsSync} from 'fs';
import type {sessionExtraOptions} from '../../common';
import {getDefaultProfile} from '../config';
export function newWindow(
options_: BrowserWindowConstructorOptions,

View file

@ -1,29 +1,14 @@
// Packages
import type {BrowserWindow, AutoUpdater} from 'electron';
import electron, {app} from 'electron';
import type {BrowserWindow, AutoUpdater as OriginalAutoUpdater} from 'electron';
import retry from 'async-retry';
import ms from 'ms';
import retry from 'async-retry';
// Utilities
import autoUpdaterLinux from './auto-updater-linux';
import {getDefaultProfile} from './config';
import {version} from './package.json';
import {getDecoratedConfig} from './plugins';
// Necessary due to typescript not handling overloads well
type AutoUpdaterEvent =
| 'error'
| 'checking-for-update'
| 'before-quit-for-update'
| 'update-downloaded'
| 'update-available'
| 'update-not-available';
interface AutoUpdater extends OriginalAutoUpdater {
on(event: AutoUpdaterEvent, listener: Function): this;
removeListener(event: AutoUpdaterEvent, listener: Function): this;
}
import autoUpdaterLinux from './auto-updater-linux';
import {getDefaultProfile} from './config';
const {platform} = process;
const isLinux = platform === 'linux';
@ -86,7 +71,7 @@ async function init() {
isInit = true;
}
const updater = (win: BrowserWindow) => {
export default (win: BrowserWindow) => {
if (!isInit) {
void init();
}
@ -94,7 +79,7 @@ const updater = (win: BrowserWindow) => {
const {rpc} = win;
const onupdate = (ev: Event, releaseNotes: string, releaseName: string, date: Date, updateUrl: string) => {
const releaseUrl = updateUrl || `https://github.com/quine-global/hyper/releases/tag/${releaseName}`;
const releaseUrl = updateUrl || `https://github.com/vercel/hyper/releases/tag/${releaseName}`;
rpc.emit('update available', {releaseNotes, releaseName, releaseUrl, canInstall: !isLinux});
};
@ -130,5 +115,3 @@ const updater = (win: BrowserWindow) => {
}
});
};
export default updater;

View file

@ -1,16 +1,13 @@
import {existsSync, readlink, symlink} from 'fs';
import path from 'path';
import {promisify} from 'util';
import {clipboard, dialog} from 'electron';
import {mkdirpSync} from 'fs-extra';
import notify from '../notify';
import {cliScriptPath, cliLinkPath} from '../config/paths';
import * as Registry from 'native-reg';
import type {ValueType} from 'native-reg';
import sudoPrompt from 'sudo-prompt';
import {cliScriptPath, cliLinkPath} from '../config/paths';
import notify from '../notify';
import {clipboard, dialog} from 'electron';
import {mkdirpSync} from 'fs-extra';
import {promisify} from 'util';
const readLink = promisify(readlink);
const symLink = promisify(symlink);

View file

@ -11,7 +11,7 @@ const generatePrefixedCommand = (command: string, shortcuts: string[]) => {
return result;
};
const mapKeys = (config: Record<string, string[] | string>) => {
export default (config: Record<string, string[] | string>) => {
return Object.keys(config).reduce((keymap: Record<string, string[]>, command: string) => {
if (!command) {
return keymap;
@ -39,5 +39,3 @@ const mapKeys = (config: Record<string, string[] | string>) => {
return keymap;
}, {});
};
export default mapKeys;

View file

@ -1,25 +0,0 @@
export const getFallBackShellConfig = (
shell: string,
shellArgs: string[],
defaultShell: string,
defaultShellArgs: string[]
): {
shell: string;
shellArgs: string[];
} | null => {
if (shellArgs.length > 0) {
return {
shell,
shellArgs: []
};
}
if (shell != defaultShell) {
return {
shell: defaultShell,
shellArgs: defaultShellArgs
};
}
return null;
};

View file

@ -4,7 +4,7 @@ import Color from 'color';
// returns a background color that's in hex
// format including the alpha channel (e.g.: `#00000050`)
// input can be any css value (rgb, hsl, string…)
const toElectronBackgroundColor = (bgColor: string) => {
export default (bgColor: string) => {
const color = Color(bgColor);
if (color.alpha() === 1) {
@ -15,5 +15,3 @@ const toElectronBackgroundColor = (bgColor: string) => {
const alphaHex = Math.round(color.alpha() * 255).toString(16);
return `#${alphaHex}${color.hex().toString().slice(1)}`;
};
export default toElectronBackgroundColor;

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,5 @@ module.exports = {
files: ['test/*'],
extensions: ['ts'],
require: ['ts-node/register/transpile-only'],
timeout: '2m',
verbose: true,
// Due to permissions issues, Windows needs cache turned off
cache: false
timeout: '30s'
};

View file

@ -1,8 +1,5 @@
module.exports = {
files: ['test/unit/*'],
extensions: ['ts'],
require: ['ts-node/register/transpile-only'],
verbose: true,
// Due to permissions issues, Windows needs cache turned off
cache: false
require: ['ts-node/register/transpile-only']
};

51
bin/cp-snapshot.js vendored
View file

@ -1,7 +1,5 @@
const path = require('path');
const fs = require('fs');
const fsPromises = require('fs/promises');
const {Arch} = require('electron-builder');
function copySnapshot(pathToElectron, archToCopy) {
@ -11,32 +9,30 @@ function copySnapshot(pathToElectron, archToCopy) {
const pathToBlobV8 = path.resolve(__dirname, '..', 'cache', archToCopy, v8ContextFileName);
console.log('Copying v8 snapshots from', pathToBlob, 'to', pathToElectron);
fs.mkdirSync(pathToElectron, { recursive: true });
fs.copyFileSync(pathToBlob, path.join(pathToElectron, snapshotFileName));
fs.copyFileSync(pathToBlobV8, path.join(pathToElectron, v8ContextFileName));
}
function getPathToElectron() {
const electronPath = require.resolve('electron');
switch (process.platform) {
case 'darwin':
return path.resolve(
electronPath,
__dirname,
'..',
'..',
'..',
'dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources'
'node_modules/electron/dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources'
);
case 'win32':
case 'linux':
return path.resolve(electronPath, '..', '..', '..', 'dist');
return path.resolve(__dirname, '..', 'node_modules', 'electron', 'dist');
}
}
function getV8ContextFileName(archToCopy) {
return `snapshot_blob.bin`;
if (process.platform === 'darwin') {
return `v8_context_snapshot${archToCopy === 'arm64' ? '.arm64' : '.x86_64'}.bin`;
} else {
return `v8_context_snapshot.bin`;
}
}
exports.default = async (context) => {
@ -46,7 +42,6 @@ exports.default = async (context) => {
? `${context.appOutDir}/Hyper.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources`
: context.appOutDir;
copySnapshot(pathToElectron, archToCopy);
useLoaderScriptFix(context);
};
if (require.main === module) {
@ -56,33 +51,3 @@ if (require.main === module) {
copySnapshot(pathToElectron, archToCopy);
}
}
// copied and modified from https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
// copied and modified from https://github.com/Adamant-im/adamant-im/blob/7b20272a717833ffb0b49b034ab9974118fc59ec/scripts/electron/sandboxFix.js
const useLoaderScriptFix = async (params) => {
if (params.electronPlatformName !== 'linux') {
// this fix is only required on linux
return
}
const executable = path.join(params.appOutDir, params.packager.executableName)
const loaderScript = `#!/usr/bin/env bash
set -u
SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
exec "$SCRIPT_DIR/${params.packager.executableName}.bin" "--no-sandbox" "$@"
`
try {
await fsPromises.rename(executable, executable + '.bin')
await fsPromises.writeFile(executable, loaderScript)
await fsPromises.chmod(executable, 0o755)
} catch (e) {
console.error('failed to create loader for sandbox fix: ' + e.message)
throw new Error('Failed to create loader for sandbox fix')
}
console.log('sandbox fix successfully applied')
}

43
bin/mk-snapshot.js vendored
View file

@ -9,18 +9,7 @@ const excludedModules = {};
const crossArchDirs = ['clang_x86_v8_arm', 'clang_x64_v8_arm64', 'win_clang_x64'];
const archMap = {
x64: 'x86_64',
arm64: 'arm64'
};
async function main() {
const npmConfigArch = process.env.npm_config_arch;
if (!npmConfigArch) {
throw new Error('env var npm_config_arch is not specified')
}
const baseDirPath = path.resolve(__dirname, '..');
console.log('Creating a linked script..');
@ -38,25 +27,11 @@ async function main() {
// Verify if we will be able to use this in `mksnapshot`
vm.runInNewContext(result.snapshotScript, undefined, {filename: snapshotScriptPath, displayErrors: true});
const outputBlobPath = `${baseDirPath}/cache/${npmConfigArch}`;
const outputBlobPath = `${baseDirPath}/cache/${process.env.npm_config_arch}`;
await mkdirp(outputBlobPath);
let mksnapshotBinPath
if (process.platform === 'win32') {
mksnapshotBinPath =
require.resolve(
path.join("electron-mksnapshot", "bin", "mksnapshot.exe")
);
} else {
mksnapshotBinPath =
require.resolve(
path.join("electron-mksnapshot", "bin", "mksnapshot")
);
}
mksnapshotBinPath = path.dirname(mksnapshotBinPath);
if (process.platform !== 'darwin') {
const mksnapshotBinPath = `${baseDirPath}/node_modules/electron-mksnapshot/bin`;
const matchingDirs = crossArchDirs.map((dir) => `${mksnapshotBinPath}/${dir}`).filter((dir) => fs.existsSync(dir));
for (const dir of matchingDirs) {
if (fs.existsSync(`${mksnapshotBinPath}/gen/v8/embedded.S`)) {
@ -65,20 +40,12 @@ async function main() {
}
}
}
const startupBlobPath = path.join(outputBlobPath, 'snapshot_blob.bin');
console.log(`Generating startup blob in "${outputBlobPath}"`);
const res = childProcess.execFileSync(
require.resolve(`electron-mksnapshot/bin/mksnapshot${process.platform === 'win32' ? '.exe' : ''}`),
[
'--startup-src=' + snapshotScriptPath,
'--startup-blob=' + startupBlobPath,
`--target-arch=${archMap[process.env.npm_config_arch]}`,
//'--v8-context-snapshot=' + v8SnapshotPath
]
childProcess.execFileSync(
path.resolve(__dirname, '..', 'node_modules', '.bin', 'mksnapshot' + (process.platform === 'win32' ? '.cmd' : '')),
[snapshotScriptPath, '--output_dir', outputBlobPath]
);
console.log('result:', res.toString())
}
main().catch((err) => console.error(err));

7
bin/notarize.js vendored
View file

@ -1,15 +1,14 @@
exports.default = async function notarizing(context) {
const { notarize } = require("@electron/notarize");
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== "darwin" || !process.env.APPLE_ID || !process.env.APPLE_PASSWORD) {
return;
}
const { notarize } = await import('@electron/notarize');
const appName = context.packager.appInfo.productFilename;
return await notarize({
appBundleId: "com.quineglobal.hyper",
appBundleId: "co.zeit.hyper",
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_PASSWORD

23
bin/snapshot-libs.js vendored
View file

@ -7,25 +7,26 @@ require('normalize-url');
require('parse-url');
require('php-escape-shell');
require('plist');
require('react-deep-force-update');
require('react-dom');
require('react-redux');
require('react');
require('redux-thunk');
require('redux');
require('reselect');
require('seamless-immutable');
require('stylis');
require('@xterm/addon-unicode11');
require('xterm-addon-unicode11');
// eslint-disable-next-line no-constant-condition
if (false) {
require('args');
require('mousetrap');
require('open');
require('react-dom');
require('react-redux');
require('react');
require('@xterm/addon-fit');
require('@xterm/addon-image');
require('@xterm/addon-search');
require('@xterm/addon-web-links');
require('@xterm/addon-webgl');
require('@xterm/addon-canvas');
require('@xterm/xterm');
require('xterm-addon-fit');
require('xterm-addon-image');
require('xterm-addon-search');
require('xterm-addon-web-links');
require('xterm-addon-webgl');
require('xterm-addon-canvas');
require('xterm');
}

View file

@ -2,20 +2,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import fs from 'fs';
import os from 'os';
import path from 'path';
import got from 'got';
import registryUrlModule from 'registry-url';
const registryUrl = registryUrlModule();
import path from 'path';
// If the user defines XDG_CONFIG_HOME they definitely want their config there,
// otherwise use the home directory in linux/mac and userdata in windows
const applicationDirectory = process.env.XDG_CONFIG_HOME
? path.join(process.env.XDG_CONFIG_HOME, 'Hyper')
: process.platform === 'win32'
? path.join(process.env.APPDATA!, 'Hyper')
: path.join(os.homedir(), '.config', 'Hyper');
? path.join(process.env.APPDATA!, 'Hyper')
: path.join(os.homedir(), '.config', 'Hyper');
const devConfigFileName = path.join(__dirname, `../hyper.json`);

View file

@ -1,20 +1,17 @@
// This is a CLI tool, using console is OK
/* eslint no-console: 0 */
import {spawn, exec} from 'child_process';
import type {SpawnOptions} from 'child_process';
import {existsSync} from 'fs';
import {spawn, exec} from 'child_process';
import {isAbsolute, resolve} from 'path';
import {existsSync} from 'fs';
import {version} from '../app/package.json';
import {promisify} from 'util';
import args from 'args';
import chalk from 'chalk';
import open from 'open';
import _columnify from 'columnify';
import got from 'got';
import open from 'open';
import ora from 'ora';
import {version} from '../app/package.json';
import * as api from './api';
let commandPromise: Promise<void> | undefined;
@ -234,7 +231,7 @@ const main = (argv: string[]) => {
options['stdio'] = 'ignore';
if (process.platform === 'darwin') {
//Use `open` to prevent multiple Hyper process
const cmd = `open -b com.quineglobal.hyper ${args_}`;
const cmd = `open -b co.zeit.hyper ${args_}`;
const opts = {
env
};

View file

@ -1,10 +1,6 @@
import type {ExecFileOptions, ExecOptions} from 'child_process';
import type {IpcMain, IpcRenderer} from 'electron';
import type parseUrl from 'parse-url';
import type {configOptions} from './config';
import type {IpcMain, IpcRenderer} from 'electron';
import type {ExecFileOptions, ExecOptions} from 'child_process';
export type Session = {
uid: string;
@ -119,12 +115,6 @@ export type IpcCommands = {
stdout: string;
stderr: string;
};
getLoadedPluginVersions: () => {name: string; version: string}[];
getPaths: () => {plugins: string[]; localPlugins: string[]};
getBasePaths: () => {path: string; localPath: string};
getDeprecatedConfig: () => Record<string, {css: string[]}>;
getDecoratedConfig: (profile: string) => configOptions;
getDecoratedKeymaps: () => Record<string, string[]>;
};
export interface IpcMainWithCommands extends IpcMain {

View file

@ -1,5 +1,6 @@
{
"appId": "com.quineglobal.hyper",
"$schema": "http://json.schemastore.org/electron-builder",
"appId": "co.zeit.hyper",
"afterSign": "./bin/notarize.js",
"afterPack": "./bin/cp-snapshot.js",
"directories": {
@ -22,6 +23,7 @@
"target": [
"deb",
"AppImage",
"rpm",
"snap",
"pacman"
]
@ -34,9 +36,7 @@
"arm64"
]
},
"signtoolOptions": {
"timeStampServer": "http://timestamp.comodoca.com"
}
"rfc3161TimeStampServer": "http://timestamp.comodoca.com"
},
"nsis": {
"include": "build/win/installer.nsh",
@ -103,6 +103,13 @@
"compression": "bzip2",
"afterInstall": "./build/linux/after-install.tpl"
},
"rpm": {
"afterInstall": "./build/linux/after-install.tpl",
"fpm": [
"--rpm-rpmbuild-define",
"_build_id_links none"
]
},
"snap": {
"confinement": "classic",
"publish": "github"

View file

@ -1,6 +1,6 @@
import type {configOptions} from '../../typings/config';
import {CONFIG_LOAD, CONFIG_RELOAD} from '../../typings/constants/config';
import type {HyperActions} from '../../typings/hyper';
import {CONFIG_LOAD, CONFIG_RELOAD} from '../constants/config';
import type {HyperActions} from '../hyper';
import type {configOptions} from '../config';
export function loadConfig(config: configOptions): HyperActions {
return {

View file

@ -1,15 +1,14 @@
import {CLOSE_TAB, CHANGE_TAB} from '../../typings/constants/tabs';
import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs';
import {
UI_WINDOW_MAXIMIZE,
UI_WINDOW_UNMAXIMIZE,
UI_OPEN_HAMBURGER_MENU,
UI_WINDOW_MINIMIZE,
UI_WINDOW_CLOSE
} from '../../typings/constants/ui';
import type {HyperDispatch} from '../../typings/hyper';
} from '../constants/ui';
import rpc from '../rpc';
import {userExitTermGroup, setActiveGroup} from './term-groups';
import type {HyperDispatch} from '../hyper';
export function closeTab(uid: string) {
return (dispatch: HyperDispatch) => {

View file

@ -1,6 +1,6 @@
import {INIT} from '../../typings/constants';
import type {HyperDispatch} from '../../typings/hyper';
import rpc from '../rpc';
import {INIT} from '../constants';
import type {HyperDispatch} from '../hyper';
export default function init() {
return (dispatch: HyperDispatch) => {

View file

@ -1,5 +1,5 @@
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../../typings/constants/notifications';
import type {HyperActions} from '../../typings/hyper';
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications';
import type {HyperActions} from '../hyper';
export function dismissNotification(id: string): HyperActions {
return {

View file

@ -1,4 +1,6 @@
import type {Session} from '../../typings/common';
import rpc from '../rpc';
import {keys} from '../utils/object';
import findBySession from '../utils/term-groups';
import {
SESSION_ADD,
SESSION_RESIZE,
@ -12,11 +14,9 @@ import {
SESSION_USER_DATA,
SESSION_SET_XTERM_TITLE,
SESSION_SEARCH
} from '../../typings/constants/sessions';
import type {HyperState, HyperDispatch, HyperActions} from '../../typings/hyper';
import rpc from '../rpc';
import {keys} from '../utils/object';
import findBySession from '../utils/term-groups';
} from '../constants/sessions';
import type {HyperState, HyperDispatch, HyperActions} from '../hyper';
import type {Session} from '../../common';
export function addSession({uid, shell, pid, cols = null, rows = null, splitDirection, activeUid, profile}: Session) {
return (dispatch: HyperDispatch, getState: () => HyperState) => {
@ -141,7 +141,7 @@ export function openSearch(uid?: string) {
dispatch({
type: SESSION_SEARCH,
uid: targetUid,
value: new Date()
value: true
});
};
}
@ -153,7 +153,7 @@ export function closeSearch(uid?: string, keyEvent?: any) {
dispatch({
type: SESSION_SEARCH,
uid: targetUid,
value: null
value: false
});
} else {
if (keyEvent) {

View file

@ -1,17 +1,16 @@
import {SESSION_REQUEST} from '../../typings/constants/sessions';
import rpc from '../rpc';
import {
DIRECTION,
TERM_GROUP_RESIZE,
TERM_GROUP_REQUEST,
TERM_GROUP_EXIT,
TERM_GROUP_EXIT_ACTIVE
} from '../../typings/constants/term-groups';
import type {ITermState, ITermGroup, HyperState, HyperDispatch, HyperActions} from '../../typings/hyper';
import rpc from '../rpc';
import {getRootGroups} from '../selectors';
} from '../constants/term-groups';
import {SESSION_REQUEST} from '../constants/sessions';
import findBySession from '../utils/term-groups';
import {getRootGroups} from '../selectors';
import {setActiveSession, ptyExitSession, userExitSession} from './sessions';
import type {ITermState, ITermGroup, HyperState, HyperDispatch, HyperActions} from '../hyper';
function requestSplit(direction: 'VERTICAL' | 'HORIZONTAL') {
return (_activeUid: string | undefined, _profile: string | undefined) =>

View file

@ -1,9 +1,10 @@
import {stat} from 'fs';
import type {Stats} from 'fs';
import type parseUrl from 'parse-url';
import {php_escapeshellcmd as escapeShellCmd} from 'php-escape-shell';
import {isExecutable} from '../utils/file';
import {getRootGroups} from '../selectors';
import findBySession from '../utils/term-groups';
import notify from '../utils/notify';
import rpc from '../rpc';
import {requestSession, sendSessionData, setActiveSession} from './sessions';
import {
UI_FONT_SIZE_SET,
UI_FONT_SIZE_INCR,
@ -23,16 +24,13 @@ import {
UI_OPEN_SSH_URL,
UI_CONTEXTMENU_OPEN,
UI_COMMAND_EXEC
} from '../../typings/constants/ui';
import type {HyperState, HyperDispatch, HyperActions, ITermGroups} from '../../typings/hyper';
import rpc from '../rpc';
import {getRootGroups} from '../selectors';
import {isExecutable} from '../utils/file';
import notify from '../utils/notify';
import findBySession from '../utils/term-groups';
} from '../constants/ui';
import {requestSession, sendSessionData, setActiveSession} from './sessions';
import {setActiveGroup} from './term-groups';
import type parseUrl from 'parse-url';
import type {HyperState, HyperDispatch, HyperActions, ITermGroups} from '../hyper';
import type {Stats} from 'fs';
import {stat} from 'fs';
export function openContextMenu(uid: string, selection: string) {
return (dispatch: HyperDispatch, getState: () => HyperState) => {

View file

@ -1,6 +1,6 @@
import {UPDATE_INSTALL, UPDATE_AVAILABLE} from '../../typings/constants/updater';
import type {HyperActions} from '../../typings/hyper';
import {UPDATE_INSTALL, UPDATE_AVAILABLE} from '../constants/updater';
import rpc from '../rpc';
import type {HyperActions} from '../hyper';
export function installUpdate(): HyperActions {
return {

View file

@ -1,7 +1,9 @@
import type {HyperDispatch} from '../typings/hyper';
import {require as remoteRequire} from '@electron/remote';
import type {HyperDispatch} from './hyper';
import {closeSearch} from './actions/sessions';
import {ipcRenderer} from './utils/ipc';
// TODO: Should be updates to new async API https://medium.com/@nornagon/electrons-remote-module-considered-harmful-70d69500f31
const {getDecoratedKeymaps} = remoteRequire('./plugins') as typeof import('../app/plugins');
let commands: Record<string, (event: any, dispatch: HyperDispatch) => void> = {
'editor:search-close': (e, dispatch) => {
@ -10,8 +12,8 @@ let commands: Record<string, (event: any, dispatch: HyperDispatch) => void> = {
}
};
export const getRegisteredKeys = async () => {
const keymaps = await ipcRenderer.invoke('getDecoratedKeymaps');
export const getRegisteredKeys = () => {
const keymaps = getDecoratedKeymaps();
return Object.keys(keymaps).reduce((result: Record<string, string>, actionName) => {
const commandKeys = keymaps[actionName];

View file

@ -1,27 +1,27 @@
import React, {forwardRef, useState} from 'react';
import React from 'react';
import type {HeaderProps} from '../../typings/hyper';
import {decorate, getTabsProps} from '../utils/plugins';
import Tabs_ from './tabs';
import type {HeaderProps} from '../hyper';
const Tabs = decorate(Tabs_, 'Tabs');
const Header = forwardRef<HTMLElement, HeaderProps>((props, ref) => {
const [headerMouseDownWindowX, setHeaderMouseDownWindowX] = useState<number>(0);
const [headerMouseDownWindowY, setHeaderMouseDownWindowY] = useState<number>(0);
export default class Header extends React.PureComponent<HeaderProps> {
headerMouseDownWindowX!: number;
headerMouseDownWindowY!: number;
const onChangeIntent = (active: string) => {
onChangeIntent = (active: string) => {
// we ignore clicks if they're a byproduct of a drag
// motion to move the window
if (window.screenX !== headerMouseDownWindowX || window.screenY !== headerMouseDownWindowY) {
if (window.screenX !== this.headerMouseDownWindowX || window.screenY !== this.headerMouseDownWindowY) {
return;
}
props.onChangeTab(active);
this.props.onChangeTab(active);
};
const handleHeaderMouseDown = () => {
handleHeaderMouseDown = () => {
// the hack of all hacks, this prevents the term
// iframe from losing focus, for example, when
// the user drags the nav around
@ -30,43 +30,43 @@ const Header = forwardRef<HTMLElement, HeaderProps>((props, ref) => {
// persist start positions of a potential drag motion
// to differentiate dragging from clicking
setHeaderMouseDownWindowX(window.screenX);
setHeaderMouseDownWindowY(window.screenY);
this.headerMouseDownWindowX = window.screenX;
this.headerMouseDownWindowY = window.screenY;
};
const handleHamburgerMenuClick = (event: React.MouseEvent) => {
handleHamburgerMenuClick = (event: React.MouseEvent) => {
let {right: x, bottom: y} = event.currentTarget.getBoundingClientRect();
x -= 15; // to compensate padding
y -= 12; // ^ same
props.openHamburgerMenu({x, y});
this.props.openHamburgerMenu({x, y});
};
const handleMaximizeClick = () => {
if (props.maximized) {
props.unmaximize();
handleMaximizeClick = () => {
if (this.props.maximized) {
this.props.unmaximize();
} else {
props.maximize();
this.props.maximize();
}
};
const handleMinimizeClick = () => {
props.minimize();
handleMinimizeClick = () => {
this.props.minimize();
};
const handleCloseClick = () => {
props.close();
handleCloseClick = () => {
this.props.close();
};
const getWindowHeaderConfig = () => {
const {showHamburgerMenu, showWindowControls} = props;
getWindowHeaderConfig() {
const {showHamburgerMenu, showWindowControls} = this.props;
const defaults = {
hambMenu: !props.isMac, // show by default on windows and linux
winCtrls: !props.isMac // show by default on Windows and Linux
hambMenu: !this.props.isMac, // show by default on windows and linux
winCtrls: !this.props.isMac // show by default on Windows and Linux
};
// don't allow the user to change defaults on macOS
if (props.isMac) {
if (this.props.isMac) {
return defaults;
}
@ -74,187 +74,186 @@ const Header = forwardRef<HTMLElement, HeaderProps>((props, ref) => {
hambMenu: showHamburgerMenu === '' ? defaults.hambMenu : showHamburgerMenu,
winCtrls: showWindowControls === '' ? defaults.winCtrls : showWindowControls
};
};
const {isMac} = props;
const {borderColor} = props;
let title = 'Hyper';
if (props.tabs.length === 1 && props.tabs[0].title) {
// if there's only one tab we use its title as the window title
title = props.tabs[0].title;
}
const {hambMenu, winCtrls} = getWindowHeaderConfig();
const left = winCtrls === 'left';
const maxButtonHref = props.maximized
? './renderer/assets/icons.svg#restore-window'
: './renderer/assets/icons.svg#maximize-window';
return (
<header
className={`header_header ${isMac && 'header_headerRounded'}`}
onMouseDown={handleHeaderMouseDown}
onMouseUp={() => window.focusActiveTerm()}
onDoubleClick={handleMaximizeClick}
ref={ref}
>
{!isMac && (
<div
className={`header_windowHeader ${props.tabs.length > 1 ? 'header_windowHeaderWithBorder' : ''}`}
style={{borderColor}}
>
{hambMenu && (
<svg
className={`header_shape ${left ? 'header_hamburgerMenuRight' : 'header_hamburgerMenuLeft'}`}
onClick={handleHamburgerMenuClick}
>
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu" />
</svg>
)}
<span className="header_appTitle">{title}</span>
{winCtrls && (
<div className={`header_windowControls ${left ? 'header_windowControlsLeft' : ''}`}>
<div className={`${left ? 'header_minimizeWindowLeft' : ''}`} onClick={handleMinimizeClick}>
<svg className="header_shape">
<use xlinkHref="./renderer/assets/icons.svg#minimize-window" />
</svg>
render() {
const {isMac} = this.props;
const props = getTabsProps(this.props, {
tabs: this.props.tabs,
borderColor: this.props.borderColor,
backgroundColor: this.props.backgroundColor,
onClose: this.props.onCloseTab,
onChange: this.onChangeIntent,
fullScreen: this.props.fullScreen,
defaultProfile: this.props.defaultProfile,
profiles: this.props.profiles.asMutable({deep: true}),
openNewTab: this.props.openNewTab
});
const {borderColor} = props;
let title = 'Hyper';
if (props.tabs.length === 1 && props.tabs[0].title) {
// if there's only one tab we use its title as the window title
title = props.tabs[0].title;
}
const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
const left = winCtrls === 'left';
const maxButtonHref = this.props.maximized
? './renderer/assets/icons.svg#restore-window'
: './renderer/assets/icons.svg#maximize-window';
return (
<header
className={`header_header ${isMac && 'header_headerRounded'}`}
onMouseDown={this.handleHeaderMouseDown}
onMouseUp={() => window.focusActiveTerm()}
onDoubleClick={this.handleMaximizeClick}
>
{!isMac && (
<div
className={`header_windowHeader ${props.tabs.length > 1 ? 'header_windowHeaderWithBorder' : ''}`}
style={{borderColor}}
>
{hambMenu && (
<svg
className={`header_shape ${left ? 'header_hamburgerMenuRight' : 'header_hamburgerMenuLeft'}`}
onClick={this.handleHamburgerMenuClick}
>
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu" />
</svg>
)}
<span className="header_appTitle">{title}</span>
{winCtrls && (
<div className={`header_windowControls ${left ? 'header_windowControlsLeft' : ''}`}>
<div className={`${left ? 'header_minimizeWindowLeft' : ''}`} onClick={this.handleMinimizeClick}>
<svg className="header_shape">
<use xlinkHref="./renderer/assets/icons.svg#minimize-window" />
</svg>
</div>
<div className={`${left ? 'header_maximizeWindowLeft' : ''}`} onClick={this.handleMaximizeClick}>
<svg className="header_shape">
<use xlinkHref={maxButtonHref} />
</svg>
</div>
<div
className={`header_closeWindow ${left ? 'header_closeWindowLeft' : ''}`}
onClick={this.handleCloseClick}
>
<svg className="header_shape">
<use xlinkHref="./renderer/assets/icons.svg#close-window" />
</svg>
</div>
</div>
<div className={`${left ? 'header_maximizeWindowLeft' : ''}`} onClick={handleMaximizeClick}>
<svg className="header_shape">
<use xlinkHref={maxButtonHref} />
</svg>
</div>
<div className={`header_closeWindow ${left ? 'header_closeWindowLeft' : ''}`} onClick={handleCloseClick}>
<svg className="header_shape">
<use xlinkHref="./renderer/assets/icons.svg#close-window" />
</svg>
</div>
</div>
)}
</div>
)}
{props.customChildrenBefore}
<Tabs
{...getTabsProps(props, {
tabs: props.tabs,
borderColor: props.borderColor,
backgroundColor: props.backgroundColor,
onClose: props.onCloseTab,
onChange: onChangeIntent,
fullScreen: props.fullScreen,
defaultProfile: props.defaultProfile,
profiles: props.profiles.asMutable({deep: true}),
openNewTab: props.openNewTab
})}
/>
{props.customChildren}
)}
</div>
)}
{this.props.customChildrenBefore}
<Tabs {...props} />
{this.props.customChildren}
<style jsx>{`
.header_header {
position: fixed;
top: 1px;
left: 1px;
right: 1px;
z-index: 100;
}
<style jsx>{`
.header_header {
position: fixed;
top: 1px;
left: 1px;
right: 1px;
z-index: 100;
}
.header_headerRounded {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.header_headerRounded {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.header_windowHeader {
height: 34px;
width: 100%;
position: fixed;
top: 1px;
left: 1px;
right: 1px;
-webkit-app-region: drag;
-webkit-user-select: none;
display: flex;
justify-content: center;
align-items: center;
}
.header_windowHeader {
height: 34px;
width: 100%;
position: fixed;
top: 1px;
left: 1px;
right: 1px;
-webkit-app-region: drag;
-webkit-user-select: none;
display: flex;
justify-content: center;
align-items: center;
}
.header_windowHeaderWithBorder {
border-color: #ccc;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.header_windowHeaderWithBorder {
border-color: #ccc;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.header_appTitle {
font-size: 12px;
}
.header_appTitle {
font-size: 12px;
}
.header_shape,
.header_shape > svg {
width: 40px;
height: 34px;
padding: 12px 15px 12px 15px;
-webkit-app-region: no-drag;
color: #fff;
opacity: 0.5;
shape-rendering: crispEdges;
}
.header_shape,
.header_shape > svg {
width: 40px;
height: 34px;
padding: 12px 15px 12px 15px;
-webkit-app-region: no-drag;
color: #fff;
opacity: 0.5;
shape-rendering: crispEdges;
}
.header_shape:hover {
opacity: 1;
}
.header_shape:hover {
opacity: 1;
}
.header_shape:active {
opacity: 0.3;
}
.header_shape:active {
opacity: 0.3;
}
.header_hamburgerMenuLeft {
position: fixed;
top: 0;
left: 0;
}
.header_hamburgerMenuLeft {
position: fixed;
top: 0;
left: 0;
}
.header_hamburgerMenuRight {
position: fixed;
top: 0;
right: 0;
}
.header_hamburgerMenuRight {
position: fixed;
top: 0;
right: 0;
}
.header_windowControls {
display: flex;
width: 120px;
height: 34px;
justify-content: space-between;
position: fixed;
top: 0;
right: 0;
}
.header_windowControls {
display: flex;
width: 120px;
height: 34px;
justify-content: space-between;
position: fixed;
top: 0;
right: 0;
}
.header_windowControlsLeft {
left: 0px;
}
.header_windowControlsLeft {
left: 0px;
}
.header_closeWindowLeft {
order: 1;
}
.header_closeWindowLeft {
order: 1;
}
.header_minimizeWindowLeft {
order: 2;
}
.header_minimizeWindowLeft {
order: 2;
}
.header_maximizeWindowLeft {
order: 3;
}
.header_maximizeWindowLeft {
order: 3;
}
.header_closeWindow:hover {
color: #fe354e;
}
.header_closeWindow:hover {
color: #fe354e;
}
.header_closeWindow:active {
color: #fe354e;
}
`}</style>
</header>
);
});
Header.displayName = 'Header';
export default Header;
.header_closeWindow:active {
color: #fe354e;
}
`}</style>
</header>
);
}
}

View file

@ -1,10 +1,8 @@
import React, {useRef, useState} from 'react';
import {VscChevronDown} from '@react-icons/all-files/vsc/VscChevronDown';
import type {configOptions} from '../config';
import useClickAway from 'react-use/lib/useClickAway';
import type {configOptions} from '../../typings/config';
interface Props {
defaultProfile: string;
profiles: configOptions['profiles'];

View file

@ -1,110 +1,115 @@
import React, {forwardRef, useEffect, useRef, useState} from 'react';
import React from 'react';
import type {NotificationProps, NotificationState} from '../hyper';
import type {NotificationProps} from '../../typings/hyper';
export default class Notification extends React.PureComponent<NotificationProps, NotificationState> {
dismissTimer!: NodeJS.Timeout;
constructor(props: NotificationProps) {
super(props);
this.state = {
dismissing: false
};
}
const Notification = forwardRef<HTMLDivElement, React.PropsWithChildren<NotificationProps>>((props, ref) => {
const dismissTimer = useRef<NodeJS.Timeout | undefined>(undefined);
const [dismissing, setDismissing] = useState(false);
componentDidMount() {
if (this.props.dismissAfter) {
this.setDismissTimer();
}
}
useEffect(() => {
setDismissTimer();
}, []);
useEffect(() => {
componentDidUpdate(prevProps: NotificationProps, prevState: NotificationState) {
// if we have a timer going and the notification text
// changed we reset the timer
resetDismissTimer();
setDismissing(false);
}, [props.text]);
if (this.props.text !== prevProps.text) {
if (prevProps.dismissAfter) {
this.resetDismissTimer();
}
if (prevState.dismissing) {
this.setState({dismissing: false});
}
}
}
const handleDismiss = () => {
setDismissing(true);
handleDismiss = () => {
this.setState({dismissing: true});
};
const onElement = (el: HTMLDivElement | null) => {
onElement = (el: HTMLDivElement | null) => {
if (el) {
el.addEventListener('webkitTransitionEnd', () => {
if (dismissing) {
props.onDismiss();
if (this.state.dismissing) {
this.props.onDismiss();
}
});
const {backgroundColor} = props;
const {backgroundColor} = this.props;
if (backgroundColor) {
el.style.setProperty('background-color', backgroundColor, 'important');
}
if (ref) {
if (typeof ref === 'function') ref(el);
else ref.current = el;
}
}
};
const setDismissTimer = () => {
if (typeof props.dismissAfter === 'number') {
dismissTimer.current = setTimeout(() => {
handleDismiss();
}, props.dismissAfter);
}
};
setDismissTimer() {
this.dismissTimer = setTimeout(() => {
this.handleDismiss();
}, this.props.dismissAfter);
}
const resetDismissTimer = () => {
clearTimeout(dismissTimer.current);
setDismissTimer();
};
resetDismissTimer() {
clearTimeout(this.dismissTimer);
this.setDismissTimer();
}
useEffect(() => {
return () => {
clearTimeout(dismissTimer.current);
};
}, []);
componentWillUnmount() {
clearTimeout(this.dismissTimer);
}
const {backgroundColor, color} = props;
const opacity = dismissing ? 0 : 1;
return (
<div ref={onElement} style={{opacity, backgroundColor, color}} className="notification_indicator">
{props.customChildrenBefore}
{props.children || props.text}
{props.userDismissable ? (
<a className="notification_dismissLink" onClick={handleDismiss} style={{color: props.userDismissColor}}>
[x]
</a>
) : null}
{props.customChildren}
render() {
const {backgroundColor, color} = this.props;
const opacity = this.state.dismissing ? 0 : 1;
return (
<div ref={this.onElement} style={{opacity, backgroundColor, color}} className="notification_indicator">
{this.props.customChildrenBefore}
{this.props.children || this.props.text}
{this.props.userDismissable ? (
<a
className="notification_dismissLink"
onClick={this.handleDismiss}
style={{color: this.props.userDismissColor}}
>
[x]
</a>
) : null}
{this.props.customChildren}
<style jsx>{`
.notification_indicator {
display: inline-block;
cursor: default;
-webkit-user-select: none;
background: rgba(255, 255, 255, 0.2);
padding: 8px 14px 9px;
margin-left: 10px;
transition: 150ms opacity ease;
color: #fff;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
}
<style jsx>{`
.notification_indicator {
display: inline-block;
cursor: default;
-webkit-user-select: none;
background: rgba(255, 255, 255, 0.2);
padding: 8px 14px 9px;
margin-left: 10px;
transition: 150ms opacity ease;
color: #fff;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
}
.notification_dismissLink {
position: relative;
left: 4px;
cursor: pointer;
font-weight: 600;
color: currentColor;
transition: font-weight 0.1s ease-in-out;
}
.notification_dismissLink {
position: relative;
left: 4px;
cursor: pointer;
font-weight: 600;
color: currentColor;
transition: font-weight 0.1s ease-in-out;
}
.notification_dismissLink:hover,
.notification_dismissLink:focus {
font-weight: 900;
}
`}</style>
</div>
);
});
Notification.displayName = 'Notification';
export default Notification;
.notification_dismissLink:hover,
.notification_dismissLink:focus {
font-weight: 900;
}
`}</style>
</div>
);
}
}

View file

@ -1,132 +1,132 @@
import React, {forwardRef} from 'react';
import React from 'react';
import type {NotificationsProps} from '../../typings/hyper';
import {decorate} from '../utils/plugins';
import Notification_ from './notification';
import type {NotificationsProps} from '../hyper';
const Notification = decorate(Notification_, 'Notification');
const Notifications = forwardRef<HTMLDivElement, NotificationsProps>((props, ref) => {
return (
<div className="notifications_view" ref={ref}>
{props.customChildrenBefore}
{props.fontShowing && (
<Notification
key="font"
backgroundColor="rgba(255, 255, 255, .2)"
text={`${props.fontSize}px`}
userDismissable={false}
onDismiss={props.onDismissFont}
dismissAfter={1000}
/>
)}
export default class Notifications extends React.PureComponent<NotificationsProps> {
render() {
return (
<div className="notifications_view">
{this.props.customChildrenBefore}
{this.props.fontShowing && (
<Notification
key="font"
backgroundColor="rgba(255, 255, 255, .2)"
text={`${this.props.fontSize}px`}
userDismissable={false}
onDismiss={this.props.onDismissFont}
dismissAfter={1000}
/>
)}
{props.resizeShowing && (
<Notification
key="resize"
backgroundColor="rgba(255, 255, 255, .2)"
text={`${props.cols}x${props.rows}`}
userDismissable={false}
onDismiss={props.onDismissResize}
dismissAfter={1000}
/>
)}
{this.props.resizeShowing && (
<Notification
key="resize"
backgroundColor="rgba(255, 255, 255, .2)"
text={`${this.props.cols}x${this.props.rows}`}
userDismissable={false}
onDismiss={this.props.onDismissResize}
dismissAfter={1000}
/>
)}
{props.messageShowing && (
<Notification
key="message"
backgroundColor="#FE354E"
color="#fff"
text={props.messageText}
onDismiss={props.onDismissMessage}
userDismissable={props.messageDismissable}
>
{props.messageURL ? (
<>
{props.messageText} (
<a
style={{color: '#fff'}}
onClick={(ev) => {
void window.require('electron').shell.openExternal(ev.currentTarget.href);
ev.preventDefault();
}}
href={props.messageURL}
>
more
</a>
)
</>
) : null}
</Notification>
)}
{props.updateShowing && (
<Notification
key="update"
backgroundColor="#18E179"
color="#000"
text={`Version ${props.updateVersion} ready`}
onDismiss={props.onDismissUpdate}
userDismissable
>
Version <b>{props.updateVersion}</b> ready.
{props.updateNote && ` ${props.updateNote.trim().replace(/\.$/, '')}`} (
<a
style={{color: '#000'}}
onClick={(ev) => {
void window.require('electron').shell.openExternal(ev.currentTarget.href);
ev.preventDefault();
}}
href={`https://github.com/quine-global/hyper/releases/tag/${props.updateVersion}`}
{this.props.messageShowing && (
<Notification
key="message"
backgroundColor="#FE354E"
color="#fff"
text={this.props.messageText}
onDismiss={this.props.onDismissMessage}
userDismissable={this.props.messageDismissable}
>
notes
</a>
).{' '}
{props.updateCanInstall ? (
{this.props.messageURL
? [
this.props.messageText,
' (',
<a
key="link"
style={{color: '#fff'}}
onClick={(ev) => {
void window.require('electron').shell.openExternal(ev.currentTarget.href);
ev.preventDefault();
}}
href={this.props.messageURL}
>
more
</a>,
') '
]
: null}
</Notification>
)}
{this.props.updateShowing && (
<Notification
key="update"
backgroundColor="#18E179"
color="#000"
text={`Version ${this.props.updateVersion} ready`}
onDismiss={this.props.onDismissUpdate}
userDismissable
>
Version <b>{this.props.updateVersion}</b> ready.
{this.props.updateNote && ` ${this.props.updateNote.trim().replace(/\.$/, '')}`} (
<a
style={{
cursor: 'pointer',
textDecoration: 'underline',
fontWeight: 'bold'
}}
onClick={props.onUpdateInstall}
>
Restart
</a>
) : (
<a
style={{
color: '#000',
cursor: 'pointer',
textDecoration: 'underline',
fontWeight: 'bold'
}}
style={{color: '#000'}}
onClick={(ev) => {
void window.require('electron').shell.openExternal(ev.currentTarget.href);
ev.preventDefault();
}}
href={props.updateReleaseUrl!}
href={`https://github.com/vercel/hyper/releases/tag/${this.props.updateVersion}`}
>
Download
notes
</a>
)}
.{' '}
</Notification>
)}
{props.customChildren}
).{' '}
{this.props.updateCanInstall ? (
<a
style={{
cursor: 'pointer',
textDecoration: 'underline',
fontWeight: 'bold'
}}
onClick={this.props.onUpdateInstall}
>
Restart
</a>
) : (
<a
style={{
color: '#000',
cursor: 'pointer',
textDecoration: 'underline',
fontWeight: 'bold'
}}
onClick={(ev) => {
void window.require('electron').shell.openExternal(ev.currentTarget.href);
ev.preventDefault();
}}
href={this.props.updateReleaseUrl!}
>
Download
</a>
)}
.{' '}
</Notification>
)}
{this.props.customChildren}
<style jsx>{`
.notifications_view {
position: fixed;
bottom: 20px;
right: 20px;
}
`}</style>
</div>
);
});
Notifications.displayName = 'Notifications';
export default Notifications;
<style jsx>{`
.notifications_view {
position: fixed;
bottom: 20px;
right: 20px;
}
`}</style>
</div>
);
}
}

View file

@ -1,15 +1,13 @@
import React, {useCallback, useRef, useEffect, forwardRef} from 'react';
import {VscArrowDown} from '@react-icons/all-files/vsc/VscArrowDown';
import React, {useCallback, useRef, useEffect} from 'react';
import type {SearchBoxProps} from '../hyper';
import {VscArrowUp} from '@react-icons/all-files/vsc/VscArrowUp';
import {VscCaseSensitive} from '@react-icons/all-files/vsc/VscCaseSensitive';
import {VscArrowDown} from '@react-icons/all-files/vsc/VscArrowDown';
import {VscClose} from '@react-icons/all-files/vsc/VscClose';
import {VscCaseSensitive} from '@react-icons/all-files/vsc/VscCaseSensitive';
import {VscRegex} from '@react-icons/all-files/vsc/VscRegex';
import {VscWholeWord} from '@react-icons/all-files/vsc/VscWholeWord';
import clsx from 'clsx';
import type {SearchBoxProps} from '../../typings/hyper';
type SearchButtonColors = {
foregroundColor: string;
selectionColor: string;
@ -84,10 +82,9 @@ const SearchButton = ({
);
};
const SearchBox = forwardRef<HTMLDivElement, SearchBoxProps>((props, ref) => {
const SearchBox = (props: SearchBoxProps) => {
const {
caseSensitive,
dateFocused,
wholeWord,
regex,
results,
@ -123,14 +120,6 @@ const SearchBox = forwardRef<HTMLDivElement, SearchBoxProps>((props, ref) => {
inputRef.current?.focus();
}, [inputRef.current]);
useEffect(() => {
if (!dateFocused) {
return;
}
inputRef.current?.focus();
inputRef.current?.select();
}, [dateFocused]);
const searchButtonColors: SearchButtonColors = {
backgroundColor: borderColor,
selectionColor,
@ -138,7 +127,7 @@ const SearchBox = forwardRef<HTMLDivElement, SearchBoxProps>((props, ref) => {
};
return (
<div className="flex-row search-container" ref={ref}>
<div className="flex-row search-container">
<div className="flex-row search-box">
<input className="search-input" type="text" onKeyDown={handleChange} ref={inputRef} placeholder="Search" />
@ -159,8 +148,8 @@ const SearchBox = forwardRef<HTMLDivElement, SearchBoxProps>((props, ref) => {
{results === undefined
? ''
: results.resultCount === 0
? 'No results'
: `${results.resultIndex + 1} of ${results.resultCount}`}
? 'No results'
: `${results.resultIndex + 1} of ${results.resultCount}`}
</span>
<div className="flex-row">
@ -239,8 +228,6 @@ const SearchBox = forwardRef<HTMLDivElement, SearchBoxProps>((props, ref) => {
</style>
</div>
);
});
SearchBox.displayName = 'SearchBox';
};
export default SearchBox;

View file

@ -1,191 +1,219 @@
import React, {useState, useEffect, useRef, forwardRef} from 'react';
import React from 'react';
import _ from 'lodash';
import type {SplitPaneProps} from '../hyper';
import sum from 'lodash/sum';
export default class SplitPane extends React.PureComponent<SplitPaneProps, {dragging: boolean}> {
dragPanePosition!: number;
dragTarget!: Element;
panes!: Element[];
paneIndex!: number;
d1!: 'height' | 'width';
d2!: 'top' | 'left';
d3!: 'clientX' | 'clientY';
panesSize!: number;
dragging!: boolean;
constructor(props: SplitPaneProps) {
super(props);
this.state = {dragging: false};
}
import type {SplitPaneProps} from '../../typings/hyper';
componentDidUpdate(prevProps: SplitPaneProps) {
if (this.state.dragging && prevProps.sizes !== this.props.sizes) {
// recompute positions for ongoing dragging
this.dragPanePosition = this.dragTarget.getBoundingClientRect()[this.d2];
}
}
const SplitPane = forwardRef<HTMLDivElement, SplitPaneProps>((props, ref) => {
const dragPanePosition = useRef<number>(0);
const dragTarget = useRef<HTMLDivElement | null>(null);
const paneIndex = useRef<number>(0);
const d1 = props.direction === 'horizontal' ? 'height' : 'width';
const d2 = props.direction === 'horizontal' ? 'top' : 'left';
const d3 = props.direction === 'horizontal' ? 'clientY' : 'clientX';
const panesSize = useRef<number | null>(null);
const [dragging, setDragging] = useState(false);
setupPanes(ev: React.MouseEvent<HTMLDivElement>) {
const target = ev.target as HTMLDivElement;
this.panes = Array.from(target.parentElement?.children || []);
this.paneIndex = this.panes.indexOf(target);
this.paneIndex -= Math.ceil(this.paneIndex / 2);
}
const handleAutoResize = (ev: React.MouseEvent<HTMLDivElement>, index: number) => {
handleAutoResize = (ev: React.MouseEvent<HTMLDivElement>) => {
ev.preventDefault();
paneIndex.current = index;
this.setupPanes(ev);
const sizes_ = getSizes();
sizes_[paneIndex.current] = 0;
sizes_[paneIndex.current + 1] = 0;
const sizes_ = this.getSizes();
sizes_[this.paneIndex] = 0;
sizes_[this.paneIndex + 1] = 0;
const availableWidth = 1 - sum(sizes_);
sizes_[paneIndex.current] = availableWidth / 2;
sizes_[paneIndex.current + 1] = availableWidth / 2;
const availableWidth = 1 - _.sum(sizes_);
sizes_[this.paneIndex] = availableWidth / 2;
sizes_[this.paneIndex + 1] = availableWidth / 2;
props.onResize(sizes_);
this.props.onResize(sizes_);
};
const handleDragStart = (ev: React.MouseEvent<HTMLDivElement>, index: number) => {
handleDragStart = (ev: React.MouseEvent<HTMLDivElement>) => {
ev.preventDefault();
setDragging(true);
window.addEventListener('mousemove', onDrag);
window.addEventListener('mouseup', onDragEnd);
this.setState({dragging: true});
window.addEventListener('mousemove', this.onDrag);
window.addEventListener('mouseup', this.onDragEnd);
// dimensions to consider
if (this.props.direction === 'horizontal') {
this.d1 = 'height';
this.d2 = 'top';
this.d3 = 'clientY';
} else {
this.d1 = 'width';
this.d2 = 'left';
this.d3 = 'clientX';
}
const target = ev.target as HTMLDivElement;
dragTarget.current = target;
dragPanePosition.current = dragTarget.current.getBoundingClientRect()[d2];
panesSize.current = target.parentElement!.getBoundingClientRect()[d1];
paneIndex.current = index;
this.dragTarget = target;
this.dragPanePosition = this.dragTarget.getBoundingClientRect()[this.d2];
this.panesSize = target.parentElement!.getBoundingClientRect()[this.d1];
this.setupPanes(ev);
};
const getSizes = () => {
const {sizes} = props;
getSizes() {
const {sizes} = this.props;
let sizes_: number[];
if (sizes) {
sizes_ = [...sizes.asMutable()];
} else {
const total = props.children.length;
const total = (this.props.children as React.ReactNodeArray).length;
const count = new Array<number>(total).fill(1 / total);
sizes_ = count;
}
return sizes_;
};
}
const onDrag = (ev: MouseEvent) => {
const sizes_ = getSizes();
onDrag = (ev: MouseEvent) => {
const sizes_ = this.getSizes();
const i = paneIndex.current;
const pos = ev[d3];
const d = Math.abs(dragPanePosition.current - pos) / panesSize.current!;
if (pos > dragPanePosition.current) {
const i = this.paneIndex;
const pos = ev[this.d3];
const d = Math.abs(this.dragPanePosition - pos) / this.panesSize;
if (pos > this.dragPanePosition) {
sizes_[i] += d;
sizes_[i + 1] -= d;
} else {
sizes_[i] -= d;
sizes_[i + 1] += d;
}
props.onResize(sizes_);
this.props.onResize(sizes_);
};
const onDragEnd = () => {
window.removeEventListener('mousemove', onDrag);
window.removeEventListener('mouseup', onDragEnd);
setDragging(false);
onDragEnd = () => {
if (this.state.dragging) {
window.removeEventListener('mousemove', this.onDrag);
window.removeEventListener('mouseup', this.onDragEnd);
this.setState({dragging: false});
}
};
useEffect(() => {
return () => {
onDragEnd();
};
}, []);
const {children, direction, borderColor} = props;
const sizeProperty = direction === 'horizontal' ? 'height' : 'width';
// workaround for the fact that if we don't specify
// sizes, sometimes flex fails to calculate the
// right height for the horizontal panes
const sizes = props.sizes || new Array<number>(children.length).fill(1 / children.length);
return (
<div className={`splitpane_panes splitpane_panes_${direction}`} ref={ref}>
{children.map((child, i) => {
const style = {
// flexBasis doesn't work for the first horizontal pane, height need to be specified
[sizeProperty]: `${sizes[i] * 100}%`,
flexBasis: `${sizes[i] * 100}%`,
flexGrow: 0
};
return (
<React.Fragment key={i}>
<div className="splitpane_pane" style={style}>
render() {
const children = this.props.children as React.ReactNodeArray;
const {direction, borderColor} = this.props;
const sizeProperty = direction === 'horizontal' ? 'height' : 'width';
// workaround for the fact that if we don't specify
// sizes, sometimes flex fails to calculate the
// right height for the horizontal panes
const sizes = this.props.sizes || new Array<number>(children.length).fill(1 / children.length);
return (
<div className={`splitpane_panes splitpane_panes_${direction}`}>
{React.Children.map(children, (child, i) => {
const style = {
// flexBasis doesn't work for the first horizontal pane, height need to be specified
[sizeProperty]: `${sizes[i] * 100}%`,
flexBasis: `${sizes[i] * 100}%`,
flexGrow: 0
};
return [
<div key="pane" className="splitpane_pane" style={style}>
{child}
</div>
{i < children.length - 1 ? (
</div>,
i < children.length - 1 ? (
<div
onMouseDown={(e) => handleDragStart(e, i)}
onDoubleClick={(e) => handleAutoResize(e, i)}
key="divider"
onMouseDown={this.handleDragStart}
onDoubleClick={this.handleAutoResize}
style={{backgroundColor: borderColor}}
className={`splitpane_divider splitpane_divider_${direction}`}
/>
) : null}
</React.Fragment>
);
})}
<div style={{display: dragging ? 'block' : 'none'}} className="splitpane_shim" />
) : null
];
})}
<div style={{display: this.state.dragging ? 'block' : 'none'}} className="splitpane_shim" />
<style jsx>{`
.splitpane_panes {
display: flex;
flex: 1;
outline: none;
position: relative;
width: 100%;
height: 100%;
}
<style jsx>{`
.splitpane_panes {
display: flex;
flex: 1;
outline: none;
position: relative;
width: 100%;
height: 100%;
}
.splitpane_panes_vertical {
flex-direction: row;
}
.splitpane_panes_vertical {
flex-direction: row;
}
.splitpane_panes_horizontal {
flex-direction: column;
}
.splitpane_panes_horizontal {
flex-direction: column;
}
.splitpane_pane {
flex: 1;
outline: none;
position: relative;
}
.splitpane_pane {
flex: 1;
outline: none;
position: relative;
}
.splitpane_divider {
box-sizing: border-box;
z-index: 1;
background-clip: padding-box;
flex-shrink: 0;
}
.splitpane_divider {
box-sizing: border-box;
z-index: 1;
background-clip: padding-box;
flex-shrink: 0;
}
.splitpane_divider_vertical {
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
width: 11px;
margin: 0 -5px;
cursor: col-resize;
}
.splitpane_divider_vertical {
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
width: 11px;
margin: 0 -5px;
cursor: col-resize;
}
.splitpane_divider_horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
width: 100%;
}
.splitpane_divider_horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
width: 100%;
}
/*
this shim is used to make sure mousemove events
trigger in all the draggable area of the screen
this is not the case due to hterm's <iframe>
*/
.splitpane_shim {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
}
`}</style>
</div>
);
});
/*
this shim is used to make sure mousemove events
trigger in all the draggable area of the screen
this is not the case due to hterm's <iframe>
*/
.splitpane_shim {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
}
`}</style>
</div>
);
}
SplitPane.displayName = 'SplitPane';
export default SplitPane;
componentWillUnmount() {
// ensure drag end
if (this.dragging) {
this.onDragEnd();
}
}
}

View file

@ -1,31 +1,24 @@
import React, {forwardRef} from 'react';
import React from 'react';
import type {StyleSheetProps} from '../hyper';
import {useDevicePixelRatio} from 'use-device-pixel-ratio';
export default class StyleSheet extends React.PureComponent<StyleSheetProps> {
render() {
const {borderColor} = this.props;
import type {StyleSheetProps} from '../../typings/hyper';
const StyleSheet = forwardRef<HTMLStyleElement, StyleSheetProps>((props, ref) => {
const {borderColor} = props;
const dpr = useDevicePixelRatio();
return (
<style jsx global ref={ref}>{`
::-webkit-scrollbar {
width: ${5 * dpr}px;
}
::-webkit-scrollbar-thumb {
-webkit-border-radius: 10px;
border-radius: 10px;
background: ${borderColor};
}
::-webkit-scrollbar-thumb:window-inactive {
background: ${borderColor};
}
`}</style>
);
});
StyleSheet.displayName = 'StyleSheet';
export default StyleSheet;
return (
<style jsx global>{`
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-thumb {
-webkit-border-radius: 10px;
border-radius: 10px;
background: ${borderColor};
}
::-webkit-scrollbar-thumb:window-inactive {
background: ${borderColor};
}
`}</style>
);
}
}

View file

@ -1,6 +1,5 @@
import React, {useEffect, useRef} from 'react';
import type {TabProps} from '../../typings/hyper';
import React from 'react';
import type {TabProps} from '../hyper';
const Tab = (props: TabProps) => {
const handleClick = (event: React.MouseEvent) => {
@ -19,16 +18,6 @@ const Tab = (props: TabProps) => {
}
};
const ref = useRef<HTMLLIElement>(null);
useEffect(() => {
if (props.lastFocused) {
ref?.current?.scrollIntoView({
behavior: 'smooth'
});
}
}, [props.lastFocused]);
const {isActive, isFirst, isLast, borderColor, hasActivity} = props;
return (
@ -39,7 +28,6 @@ const Tab = (props: TabProps) => {
className={`tab_tab ${isFirst ? 'tab_first' : ''} ${isActive ? 'tab_active' : ''} ${
isFirst && isActive ? 'tab_firstActive' : ''
} ${hasActivity ? 'tab_hasActivity' : ''}`}
ref={ref}
>
{props.customChildrenBefore}
<span
@ -70,7 +58,6 @@ const Tab = (props: TabProps) => {
list-style-type: none;
flex-grow: 1;
position: relative;
min-width: 10em;
}
.tab_tab:hover {
@ -174,6 +161,4 @@ const Tab = (props: TabProps) => {
);
};
Tab.displayName = 'Tab';
export default Tab;

View file

@ -1,40 +1,21 @@
import React, {forwardRef, useEffect, useState} from 'react';
import React from 'react';
import debounce from 'lodash/debounce';
import type {ITab, TabsProps} from '../../typings/hyper';
import {decorate, getTabProps} from '../utils/plugins';
import DropdownButton from './new-tab';
import Tab_ from './tab';
import type {TabsProps} from '../hyper';
import DropdownButton from './new-tab';
const Tab = decorate(Tab_, 'Tab');
const isMac = /Mac/.test(navigator.userAgent);
const Tabs = forwardRef<HTMLElement, TabsProps>((props, ref) => {
const Tabs = (props: TabsProps) => {
const {tabs = [], borderColor, onChange, onClose, fullScreen} = props;
const [shouldFocusCounter, setShouldFocusCounter] = useState({
index: 0,
when: undefined as Date | undefined
});
const scrollToActiveTab = debounce((currTabs: ITab[]) => {
const activeTab = currTabs.findIndex((t) => t.isActive);
setShouldFocusCounter({
index: activeTab,
when: new Date()
});
}, 100);
useEffect(() => {
scrollToActiveTab(tabs);
}, [tabs, tabs.length]);
const hide = !isMac && tabs.length === 1;
return (
<nav className={`tabs_nav ${hide ? 'tabs_hiddenNav' : ''}`} ref={ref}>
<nav className={`tabs_nav ${hide ? 'tabs_hiddenNav' : ''}`}>
{props.customChildrenBefore}
{tabs.length === 1 && isMac ? <div className="tabs_title">{tabs[0].title}</div> : null}
{tabs.length > 1 ? (
@ -50,12 +31,8 @@ const Tabs = forwardRef<HTMLElement, TabsProps>((props, ref) => {
isActive,
hasActivity,
onSelect: onChange.bind(null, uid),
onClose: onClose.bind(null, uid),
lastFocused: undefined as Date | undefined
onClose: onClose.bind(null, uid)
});
if (shouldFocusCounter.index === i) {
tabProps.lastFocused = shouldFocusCounter.when;
}
return <Tab key={`tab-${uid}`} {...tabProps} />;
})}
</ul>
@ -108,12 +85,6 @@ const Tabs = forwardRef<HTMLElement, TabsProps>((props, ref) => {
flex-flow: row;
margin-left: ${isMac ? '76px' : '0'};
flex-grow: 1;
overflow-x: auto;
}
.tabs_list::-webkit-scrollbar,
.tabs_list::-webkit-scrollbar-button {
display: none;
}
.tabs_fullScreen {
@ -135,8 +106,6 @@ const Tabs = forwardRef<HTMLElement, TabsProps>((props, ref) => {
`}</style>
</nav>
);
});
Tabs.displayName = 'Tabs';
};
export default Tabs;

View file

@ -1,13 +1,10 @@
import React from 'react';
import {connect} from 'react-redux';
import type {HyperState, HyperDispatch, TermGroupProps, TermGroupOwnProps} from '../../typings/hyper';
import {resizeTermGroup} from '../actions/term-groups';
import {decorate, getTermProps, getTermGroupProps} from '../utils/plugins';
import SplitPane_ from './split-pane';
import {resizeTermGroup} from '../actions/term-groups';
import Term_ from './term';
import SplitPane_ from './split-pane';
import type {HyperState, HyperDispatch, TermGroupProps, TermGroupOwnProps} from '../hyper';
const Term = decorate(Term_, 'Term');
const SplitPane = decorate(SplitPane_, 'SplitPane');

View file

@ -1,29 +1,24 @@
import {clipboard, shell} from 'electron';
import React from 'react';
import {CanvasAddon} from '@xterm/addon-canvas';
import {FitAddon} from '@xterm/addon-fit';
import {ImageAddon} from '@xterm/addon-image';
import {LigaturesAddon} from '@xterm/addon-ligatures';
import {SearchAddon} from '@xterm/addon-search';
import type {ISearchDecorationOptions} from '@xterm/addon-search';
import {Unicode11Addon} from '@xterm/addon-unicode11';
import {WebLinksAddon} from '@xterm/addon-web-links';
import {WebglAddon} from '@xterm/addon-webgl';
import {Terminal} from '@xterm/xterm';
import type {ITerminalOptions, IDisposable} from '@xterm/xterm';
import type {ITerminalOptions, IDisposable} from 'xterm';
import {Terminal} from 'xterm';
import {FitAddon} from 'xterm-addon-fit';
import {WebLinksAddon} from 'xterm-addon-web-links';
import type {ISearchDecorationOptions} from 'xterm-addon-search';
import {SearchAddon} from 'xterm-addon-search';
import {WebglAddon} from 'xterm-addon-webgl';
import {CanvasAddon} from 'xterm-addon-canvas';
import {LigaturesAddon} from 'xterm-addon-ligatures';
import {Unicode11Addon} from 'xterm-addon-unicode11';
import {clipboard, shell} from 'electron';
import Color from 'color';
import isEqual from 'lodash/isEqual';
import pickBy from 'lodash/pickBy';
import type {TermProps} from '../../typings/hyper';
import terms from '../terms';
import processClipboard from '../utils/paste';
import {decorate} from '../utils/plugins';
import _SearchBox from './searchBox';
import '@xterm/xterm/css/xterm.css';
import type {TermProps} from '../hyper';
import {pickBy, isEqual} from 'lodash';
import {decorate} from '../utils/plugins';
import 'xterm/css/xterm.css';
import {ImageAddon} from 'xterm-addon-image';
const SearchBox = decorate(_SearchBox, 'SearchBox');
@ -518,7 +513,6 @@ export default class Term extends React.PureComponent<
{this.props.customChildren}
{this.props.search ? (
<SearchBox
dateFocused={this.props.search}
next={this.searchNext}
prev={this.searchPrevious}
close={this.closeSearchBox}

View file

@ -1,20 +1,18 @@
import React from 'react';
import type {TermsProps, HyperDispatch} from '../../typings/hyper';
import {registerCommandHandlers} from '../command-registry';
import {ObjectTypedKeys} from '../utils/object';
import {decorate, getTermGroupProps} from '../utils/plugins';
import StyleSheet_ from './style-sheet';
import type Term from './term';
import {registerCommandHandlers} from '../command-registry';
import TermGroup_ from './term-group';
import StyleSheet_ from './style-sheet';
import type {TermsProps, HyperDispatch} from '../hyper';
import type Term from './term';
import {ObjectTypedKeys} from '../utils/object';
const TermGroup = decorate(TermGroup_, 'TermGroup');
const StyleSheet = decorate(StyleSheet_, 'StyleSheet');
const isMac = /Mac/.test(navigator.userAgent);
export default class Terms extends React.Component<React.PropsWithChildren<TermsProps>> {
export default class Terms extends React.Component<TermsProps> {
terms: Record<string, Term>;
registerCommands: (cmds: Record<string, (e: any, dispatch: HyperDispatch) => void>) => void;
constructor(props: TermsProps, context: any) {

View file

@ -1,4 +1,4 @@
import type {FontWeight} from '@xterm/xterm';
import type {FontWeight} from 'xterm';
export type ColorMap = {
black: string;

View file

@ -74,7 +74,7 @@ export interface SessionSetCwdAction {
export interface SessionSearchAction {
type: typeof SESSION_SEARCH;
uid: string;
value: Date | null;
value: boolean;
}
export type SessionActions =

View file

@ -2,10 +2,10 @@ export const TERM_GROUP_REQUEST = 'TERM_GROUP_REQUEST';
export const TERM_GROUP_EXIT = 'TERM_GROUP_EXIT';
export const TERM_GROUP_RESIZE = 'TERM_GROUP_RESIZE';
export const TERM_GROUP_EXIT_ACTIVE = 'TERM_GROUP_EXIT_ACTIVE';
export enum DIRECTION {
HORIZONTAL = 'HORIZONTAL',
VERTICAL = 'VERTICAL'
}
export const DIRECTION = {
HORIZONTAL: 'HORIZONTAL',
VERTICAL: 'VERTICAL'
} as const;
export interface TermGroupRequestAction {
type: typeof TERM_GROUP_REQUEST;

View file

@ -1,11 +1,11 @@
import {createSelector} from 'reselect';
import type {HyperState, HyperDispatch, ITab} from '../../typings/hyper';
import {closeTab, changeTab, maximize, openHamburgerMenu, unmaximize, minimize, close} from '../actions/header';
import {requestTermGroup} from '../actions/term-groups';
import Header from '../components/header';
import {getRootGroups} from '../selectors';
import {closeTab, changeTab, maximize, openHamburgerMenu, unmaximize, minimize, close} from '../actions/header';
import {connect} from '../utils/plugins';
import {getRootGroups} from '../selectors';
import type {HyperState, HyperDispatch, ITab} from '../hyper';
import {requestTermGroup} from '../actions/term-groups';
const isMac = /Mac/.test(navigator.userAgent);

View file

@ -1,142 +1,145 @@
import React, {forwardRef, useEffect, useRef} from 'react';
import Mousetrap from 'mousetrap';
import React from 'react';
import type {MousetrapInstance} from 'mousetrap';
import stylis from 'stylis';
import Mousetrap from 'mousetrap';
import type {HyperState, HyperProps, HyperDispatch} from '../../typings/hyper';
import {connect} from '../utils/plugins';
import * as uiActions from '../actions/ui';
import {getRegisteredKeys, getCommandHandler, shouldPreventDefault} from '../command-registry';
import type Terms from '../components/terms';
import {connect} from '../utils/plugins';
import stylis from 'stylis';
import {HeaderContainer} from './header';
import NotificationsContainer from './notifications';
import TermsContainer from './terms';
import NotificationsContainer from './notifications';
import type {HyperState, HyperProps, HyperDispatch} from '../hyper';
import type Terms from '../components/terms';
const isMac = /Mac/.test(navigator.userAgent);
const Hyper = forwardRef<HTMLDivElement, HyperProps>((props, ref) => {
const mousetrap = useRef<MousetrapInstance | null>(null);
const terms = useRef<Terms | null>(null);
class Hyper extends React.PureComponent<HyperProps> {
mousetrap!: MousetrapInstance;
terms!: Terms;
constructor(props: HyperProps) {
super(props);
}
useEffect(() => {
void attachKeyListeners();
}, [props.lastConfigUpdate]);
useEffect(() => {
handleFocusActive(props.activeSession);
}, [props.activeSession]);
componentDidUpdate(prev: HyperProps) {
const {lastConfigUpdate} = this.props;
if (lastConfigUpdate && lastConfigUpdate !== prev.lastConfigUpdate) {
this.attachKeyListeners();
}
if (prev.activeSession !== this.props.activeSession) {
this.handleFocusActive(this.props.activeSession);
}
}
const handleFocusActive = (uid?: string | null) => {
const term = uid && terms.current?.getTermByUid(uid);
handleFocusActive = (uid?: string | null) => {
const term = uid && this.terms.getTermByUid(uid);
if (term) {
term.focus();
}
};
const handleSelectAll = () => {
const term = terms.current?.getActiveTerm();
handleSelectAll = () => {
const term = this.terms.getActiveTerm();
if (term) {
term.selectAll();
}
};
const attachKeyListeners = async () => {
if (!mousetrap.current) {
attachKeyListeners() {
if (!this.mousetrap) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
mousetrap.current = new (Mousetrap as any)(window, true);
mousetrap.current!.stopCallback = () => {
this.mousetrap = new (Mousetrap as any)(window, true);
this.mousetrap.stopCallback = () => {
// All events should be intercepted even if focus is in an input/textarea
return false;
};
} else {
mousetrap.current.reset();
this.mousetrap.reset();
}
const keys = await getRegisteredKeys();
const keys = getRegisteredKeys();
Object.keys(keys).forEach((commandKeys) => {
mousetrap.current?.bind(
this.mousetrap.bind(
commandKeys,
(e) => {
const command = keys[commandKeys];
// We should tell xterm to ignore this event.
// We should tell to xterm that it should ignore this event.
(e as any).catched = true;
props.execCommand(command, getCommandHandler(command), e);
this.props.execCommand(command, getCommandHandler(command), e);
shouldPreventDefault(command) && e.preventDefault();
},
'keydown'
);
});
};
}
useEffect(() => {
void attachKeyListeners();
window.rpc.on('term selectAll', handleSelectAll);
}, []);
componentDidMount() {
this.attachKeyListeners();
window.rpc.on('term selectAll', this.handleSelectAll);
}
const onTermsRef = (_terms: Terms | null) => {
terms.current = _terms;
onTermsRef = (terms: Terms) => {
this.terms = terms;
window.focusActiveTerm = (uid?: string) => {
if (uid) {
handleFocusActive(uid);
this.handleFocusActive(uid);
} else {
terms.current?.getActiveTerm()?.focus();
this.terms.getActiveTerm().focus();
}
};
};
useEffect(() => {
return () => {
mousetrap.current?.reset();
};
}, []);
componentWillUnmount() {
this.mousetrap?.reset();
}
const {isMac: isMac_, customCSS, uiFontFamily, borderColor, maximized, fullScreen} = props;
const borderWidth = isMac_ ? '' : `${maximized ? '0' : '1'}px`;
stylis.set({prefix: false});
return (
<div id="hyper" ref={ref}>
<div
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
className={`hyper_main ${isMac_ && 'hyper_mainRounded'} ${fullScreen ? 'fullScreen' : ''}`}
>
<HeaderContainer />
<TermsContainer ref_={onTermsRef} />
{props.customInnerChildren}
render() {
const {isMac: isMac_, customCSS, uiFontFamily, borderColor, maximized, fullScreen} = this.props;
const borderWidth = isMac_ ? '' : `${maximized ? '0' : '1'}px`;
stylis.set({prefix: false});
return (
<div id="hyper">
<div
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
className={`hyper_main ${isMac_ && 'hyper_mainRounded'} ${fullScreen ? 'fullScreen' : ''}`}
>
<HeaderContainer />
<TermsContainer ref_={this.onTermsRef} />
{this.props.customInnerChildren}
</div>
<NotificationsContainer />
{this.props.customChildren}
<style jsx>
{`
.hyper_main {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid #333;
}
.hyper_mainRounded {
border-radius: 10.5px;
overflow: hidden;
}
`}
</style>
{/*
Add custom CSS to Hyper.
We add a scope to the customCSS so that it can get around the weighting applied by styled-jsx
*/}
<style dangerouslySetInnerHTML={{__html: stylis('#hyper', customCSS)}} />
</div>
<NotificationsContainer />
{props.customChildren}
<style jsx>
{`
.hyper_main {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid #333;
}
.hyper_mainRounded {
border-radius: 10.5px;
overflow: hidden;
}
`}
</style>
{/*
Add custom CSS to Hyper.
We add a scope to the customCSS so that it can get around the weighting applied by styled-jsx
*/}
<style dangerouslySetInnerHTML={{__html: stylis('#hyper', customCSS)}} />
</div>
);
});
Hyper.displayName = 'Hyper';
);
}
}
const mapStateToProps = (state: HyperState) => {
return {

View file

@ -1,8 +1,8 @@
import type {HyperState, HyperDispatch} from '../../typings/hyper';
import {dismissNotification} from '../actions/notifications';
import {installUpdate} from '../actions/updater';
import Notifications from '../components/notifications';
import {installUpdate} from '../actions/updater';
import {connect} from '../utils/plugins';
import {dismissNotification} from '../actions/notifications';
import type {HyperState, HyperDispatch} from '../hyper';
const mapStateToProps = (state: HyperState) => {
const {ui} = state;

View file

@ -1,4 +1,5 @@
import type {HyperState, HyperDispatch} from '../../typings/hyper';
import Terms from '../components/terms';
import {connect} from '../utils/plugins';
import {
resizeSession,
sendSessionData,
@ -7,10 +8,10 @@ import {
openSearch,
closeSearch
} from '../actions/sessions';
import {openContextMenu} from '../actions/ui';
import Terms from '../components/terms';
import {getRootGroups} from '../selectors';
import {connect} from '../utils/plugins';
import type {HyperState, HyperDispatch} from '../hyper';
const mapStateToProps = (state: HyperState) => {
const {sessions} = state.sessions;

7
lib/ext-modules.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
declare module 'php-escape-shell' {
export function php_escapeshellcmd(path: string): string;
}
declare module 'react-deep-force-update' {
export default function (...args: any[]): any;
}

View file

@ -1,7 +1,5 @@
// eslint-disable-next-line eslint-comments/disable-enable-pair
/* eslint-disable import/order */
import type {Immutable, ImmutableDate} from 'seamless-immutable';
import type Client from '../lib/utils/rpc';
import type {Immutable} from 'seamless-immutable';
import type Client from './utils/rpc';
declare global {
interface Window {
@ -41,7 +39,7 @@ export type ITermState = Immutable<{
}>;
export type cursorShapes = 'BEAM' | 'UNDERLINE' | 'BLOCK';
import type {FontWeight, IWindowsPty, Terminal} from '@xterm/xterm';
import type {FontWeight, IWindowsPty, Terminal} from 'xterm';
import type {ColorMap, configOptions} from './config';
export type uiState = Immutable<{
@ -118,7 +116,7 @@ export type session = {
pid: number | null;
resizeAt?: number;
rows: number | null;
search: ImmutableDate | null;
search: boolean;
shell: string | null;
title: string;
uid: string;
@ -189,10 +187,10 @@ export type HyperActions = (
| TabActions
) & {effect?: () => void};
import type configureStore from '../lib/store/configure-store';
import type configureStore from './store/configure-store';
export type HyperDispatch = ReturnType<typeof configureStore>['dispatch'];
import type {ReactChild, ReactNode} from 'react';
import type {ReactChild} from 'react';
type extensionProps = Partial<{
customChildren: ReactChild | ReactChild[];
customChildrenBefore: ReactChild | ReactChild[];
@ -200,17 +198,17 @@ type extensionProps = Partial<{
customInnerChildren: ReactChild | ReactChild[];
}>;
import type {HeaderConnectedProps} from '../lib/containers/header';
import type {HeaderConnectedProps} from './containers/header';
export type HeaderProps = HeaderConnectedProps & extensionProps;
import type {HyperConnectedProps} from '../lib/containers/hyper';
import type {HyperConnectedProps} from './containers/hyper';
export type HyperProps = HyperConnectedProps & extensionProps;
import type {NotificationsConnectedProps} from '../lib/containers/notifications';
import type {NotificationsConnectedProps} from './containers/notifications';
export type NotificationsProps = NotificationsConnectedProps & extensionProps;
import type Terms from '../lib/components/terms';
import type {TermsConnectedProps} from '../lib/containers/terms';
import type Terms from './components/terms';
import type {TermsConnectedProps} from './containers/terms';
export type TermsProps = TermsConnectedProps & extensionProps & {ref_: (terms: Terms | null) => void};
export type StyleSheetProps = {
@ -230,7 +228,6 @@ export type TabProps = {
onClose: () => void;
onSelect: () => void;
text: string;
lastFocused: Date | undefined;
} & extensionProps;
export type ITab = {
@ -262,15 +259,18 @@ export type NotificationProps = {
userDismissColor?: string;
} & extensionProps;
export type NotificationState = {
dismissing: boolean;
};
export type SplitPaneProps = {
borderColor: string;
direction: 'horizontal' | 'vertical';
onResize: (sizes: number[]) => void;
onResize: Function;
sizes?: Immutable<number[]> | null;
children: ReactNode[];
};
import type Term from '../lib/components/term';
import type Term from './components/term';
export type TermGroupOwnProps = {
cursorAccentColor?: string;
@ -322,11 +322,10 @@ export type TermGroupOwnProps = {
| 'imageSupport'
>;
import type {TermGroupConnectedProps} from '../lib/components/term-group';
import type {TermGroupConnectedProps} from './components/term-group';
export type TermGroupProps = TermGroupConnectedProps & TermGroupOwnProps;
export type SearchBoxProps = {
dateFocused: ImmutableDate | null;
caseSensitive: boolean;
wholeWord: boolean;
regex: boolean;
@ -344,8 +343,8 @@ export type SearchBoxProps = {
font: string;
};
import type {FitAddon} from '@xterm/addon-fit';
import type {SearchAddon} from '@xterm/addon-search';
import type {FitAddon} from 'xterm-addon-fit';
import type {SearchAddon} from 'xterm-addon-search';
export type TermProps = {
backgroundColor: string;
bell: 'SOUND' | false;
@ -387,7 +386,7 @@ export type TermProps = {
rows: number | null;
screenReaderMode: boolean;
scrollback: number;
search: ImmutableDate | null;
search: boolean;
searchAddon: SearchAddon | null;
selectionColor: string;
term: Terminal | null;

Some files were not shown because too many files have changed in this diff Show more