mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-13 04:28:41 -09:00
* npm: add .npmrc with save-exact=true * split panes: create initial implementation This allows users to split their Hyperterm terms into multiple nested splits, both vertical and horizontal. Fixes #56 * split panes: suport closing tabs and individual panes * split panes: ensure new splits are placed at the correct index New split panes should be placed after the currently active pane, not at the end like they were previously. * split panes: add explicit dependency to uuid * split panes: implement split pane cycling This adds menu buttons for moving back and forward between open split panes in the currect terminal tab. Doesn't add a hotkey yet, needs some bikeshedding. * split panes: move activeSessionUid to its own object It made little sense to have so many objects with `activeSessionUid` set to `null` when it only mattered on the top level. Now it's an object mapping term-group `uid` to `sessionUid` instead. * split panes: make sure closing the last split pane exits the app * split panes: fix a crash after closing specific panes Sometimes the terminal would crash when a specific split pane was closed, because the `activeSessions` mapping wasn't updated correctly. * split panes: fix a bug that caused initial session sizing to be wrong * fix all our focus / blur issues in one fell swoop :O (famous last words) * get rid of react warning * hterm: make sure not to lose focus when VT listens on clicks * term: restore onactive callback * add missing `return` to override (just in case) * split pane: new split pane implementation * goodbye react-split-pane * added term group resizing action and reducer * terms: supply border color so that we can use it for splits * term-group: add resizing hook * term-groups: add resizing constant * remove split pane css side-effect * split panes: pass existing hterm instances to Term * split panes: add keybindings for split pane cycling * split panes: remove unused action * split panes: remove unused styling * split-pane: remove `console.log` * split-pane: remove `console.log` * split panes: rebalance sizes on insert/removal * split panes: pass existing hterm instances to Term * split panes: add keybindings for split pane cycling * split panes: remove unused action * split panes: remove unused styling * split panes: rebalance sizes on insert/removal * split panes: set a minimum size for resizing * split-pane: fix vertical splits * css :| * package: bump electron * split panes: attach onFocus listener to webviews * 1.4.1 and 1.4.2 are broken. they have the following regression: - open google.com on the main window - open a new tab - come back to previous tab. webview is gone :| * split panes: handle PTY exits * split panes: add linux friendly keybindings
173 lines
4.8 KiB
JavaScript
173 lines
4.8 KiB
JavaScript
import rpc from '../rpc';
|
|
import {
|
|
DIRECTION,
|
|
TERM_GROUP_RESIZE,
|
|
TERM_GROUP_REQUEST,
|
|
TERM_GROUP_EXIT,
|
|
TERM_GROUP_EXIT_ACTIVE
|
|
} 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';
|
|
|
|
function requestSplit(direction) {
|
|
return () => (dispatch, getState) => {
|
|
const {ui} = getState();
|
|
dispatch({
|
|
type: SESSION_REQUEST,
|
|
effect: () => {
|
|
rpc.emit('new', {
|
|
splitDirection: direction,
|
|
cwd: ui.cwd
|
|
});
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
export const requestVerticalSplit = requestSplit(DIRECTION.VERTICAL);
|
|
export const requestHorizontalSplit = requestSplit(DIRECTION.HORIZONTAL);
|
|
|
|
export function resizeTermGroup(uid, sizes) {
|
|
return {
|
|
uid,
|
|
type: TERM_GROUP_RESIZE,
|
|
sizes
|
|
};
|
|
}
|
|
|
|
export function requestTermGroup() {
|
|
return (dispatch, getState) => {
|
|
const {ui} = getState();
|
|
const {cols, rows, cwd} = ui;
|
|
dispatch({
|
|
type: TERM_GROUP_REQUEST,
|
|
effect: () => {
|
|
rpc.emit('new', {
|
|
isNewGroup: true,
|
|
cols,
|
|
rows,
|
|
cwd
|
|
});
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
export function setActiveGroup(uid) {
|
|
return (dispatch, getState) => {
|
|
const {termGroups} = getState();
|
|
dispatch(setActiveSession(termGroups.activeSessions[uid]));
|
|
};
|
|
}
|
|
|
|
// When we've found the next group which we want to
|
|
// set as active (after closing something), we also need
|
|
// to find the first child group which has a sessionUid.
|
|
const findFirstSession = (state, group) => {
|
|
if (group.sessionUid) {
|
|
return group.sessionUid;
|
|
}
|
|
|
|
for (const childUid of group.children) {
|
|
const child = state.termGroups[childUid];
|
|
// We want to find the *leftmost* session,
|
|
// even if it's nested deep down:
|
|
const sessionUid = findFirstSession(state, child);
|
|
if (sessionUid) {
|
|
return sessionUid;
|
|
}
|
|
}
|
|
};
|
|
|
|
const findPrevious = (list, old) => {
|
|
const index = list.indexOf(old);
|
|
// If `old` was the first item in the list,
|
|
// choose the other item available:
|
|
return index ? list[index - 1] : list[1];
|
|
};
|
|
|
|
const findNextSessionUid = (state, group) => {
|
|
// If we're closing a root group (i.e. a whole tab),
|
|
// the next group needs to be a root group as well:
|
|
if (state.activeRootGroup === group.uid) {
|
|
const rootGroups = getRootGroups({termGroups: state});
|
|
const nextGroup = findPrevious(rootGroups, group);
|
|
return findFirstSession(state, nextGroup);
|
|
}
|
|
|
|
const {children} = state.termGroups[group.parentUid];
|
|
const nextUid = findPrevious(children, group.uid);
|
|
return findFirstSession(state, state.termGroups[nextUid]);
|
|
};
|
|
|
|
export function ptyExitTermGroup(sessionUid) {
|
|
return (dispatch, getState) => {
|
|
const {termGroups} = getState();
|
|
const group = findBySession(termGroups, sessionUid);
|
|
// This might have already been closed:
|
|
if (!group) {
|
|
return dispatch(ptyExitSession(sessionUid));
|
|
}
|
|
|
|
dispatch({
|
|
type: TERM_GROUP_EXIT,
|
|
uid: group.uid,
|
|
effect: () => {
|
|
const activeSessionUid = termGroups.activeSessions[termGroups.activeRootGroup];
|
|
if (Object.keys(termGroups.termGroups).length > 1 && activeSessionUid === sessionUid) {
|
|
const nextSessionUid = findNextSessionUid(termGroups, group);
|
|
dispatch(setActiveSession(nextSessionUid));
|
|
}
|
|
|
|
dispatch(ptyExitSession(sessionUid));
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
export function userExitTermGroup(uid) {
|
|
return (dispatch, getState) => {
|
|
const {termGroups} = getState();
|
|
dispatch({
|
|
type: TERM_GROUP_EXIT,
|
|
uid,
|
|
effect: () => {
|
|
const group = termGroups.termGroups[uid];
|
|
if (Object.keys(termGroups.termGroups).length <= 1) {
|
|
// No need to attempt finding a new active session
|
|
// if this is the last one we've got:
|
|
return dispatch(userExitSession(group.sessionUid));
|
|
}
|
|
|
|
const activeSessionUid = termGroups.activeSessions[termGroups.activeRootGroup];
|
|
if (termGroups.activeRootGroup === uid || activeSessionUid === group.sessionUid) {
|
|
const nextSessionUid = findNextSessionUid(termGroups, group);
|
|
dispatch(setActiveSession(nextSessionUid));
|
|
}
|
|
|
|
if (group.sessionUid) {
|
|
dispatch(userExitSession(group.sessionUid));
|
|
} else {
|
|
group.children.forEach(childUid => {
|
|
dispatch(userExitTermGroup(childUid));
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
export function exitActiveTermGroup() {
|
|
return (dispatch, getState) => {
|
|
dispatch({
|
|
type: TERM_GROUP_EXIT_ACTIVE,
|
|
effect() {
|
|
const {sessions, termGroups} = getState();
|
|
const {uid} = findBySession(termGroups, sessions.activeUid);
|
|
dispatch(userExitTermGroup(uid));
|
|
}
|
|
});
|
|
};
|
|
}
|