Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ When you put a `console.log()` in your plugin code, it will be displayed in the
Almost all available API methods can be found on https://hyper.is.
If there's any missing, let us know or submit a PR to document it!

### Xterm Addons

Plugins can contribute xterm.js addons by exporting a `getXtermAddons` function that returns an array of addon instances.

Example:

```js
// In your plugin's main file
import { xyzAddon } from 'xterm-addon-xyz';

exports.getXtermAddons = () => {
return [new xyzAddon()];
};
```

These addons are loaded by Hyper and made available to the terminal.

### Components
You can decorate almost all Hyper components with a Higher-Order Component (HOC). To understand their architecture, the easiest way is to use React dev-tools to dig in to their hierarchy.

Expand Down
3 changes: 2 additions & 1 deletion app/plugins/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export const availableExtensions = new Set([
'decorateBrowserOptions',
'decorateMenu',
'decorateTerm',
'decorateTerms',
'decorateHyper',
'decorateHyperTerm', // for backwards compatibility with hyperterm
'decorateHeader',
'decorateTerms',
'decorateTab',
'decorateNotification',
'decorateNotifications',
Expand All @@ -31,6 +31,7 @@ export const availableExtensions = new Set([
'getTabProps',
'getTabsProps',
'getTermGroupProps',
'getXtermAddons',
'mapHyperTermState',
'mapTermsState',
'mapHeaderState',
Expand Down
12 changes: 11 additions & 1 deletion lib/components/term.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {WebglAddon} from 'xterm-addon-webgl';
import type {TermProps} from '../../typings/hyper';
import terms from '../terms';
import processClipboard from '../utils/paste';
import {decorate} from '../utils/plugins';
import {decorate, getXtermAddons} from '../utils/plugins';

import _SearchBox from './searchBox';

Expand Down Expand Up @@ -243,6 +243,16 @@ export default class Term extends React.PureComponent<
if (props.imageSupport) {
this.term.loadAddon(new ImageAddon());
}

// Load xterm addons from plugins
const pluginAddons = getXtermAddons();
for (const addon of pluginAddons) {
try {
this.term.loadAddon(addon);
} catch (err) {
console.warn(`Failed to load xterm addon from plugin:`, err);
}
}
} else {
// get the cached plugins
this.fitAddon = props.fitAddon!;
Expand Down
17 changes: 17 additions & 0 deletions lib/utils/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ let reducersDecorators: {
reduceTermGroups: ITermGroupReducer[];
};

// xterm addons from plugins
let xtermAddons: any[] = [];

// expose decorated component instance to the higher-order components
function exposeDecorated<P extends Record<string, any>>(
Component_: React.ComponentType<P>
Expand Down Expand Up @@ -252,6 +255,7 @@ const loadModules = () => {
tabsPropsDecorators = [];
termPropsDecorators = [];
termGroupPropsDecorators = [];
xtermAddons = [];

propsDecorators = {
getTermProps: termPropsDecorators,
Expand Down Expand Up @@ -355,6 +359,15 @@ const loadModules = () => {
connectors.Notifications.dispatch.push(mod.mapNotificationsDispatch);
}

if (mod.getXtermAddons) {
const addons = mod.getXtermAddons();
if (Array.isArray(addons)) {
xtermAddons.push(...addons);
} else if (addons) {
xtermAddons.push(addons);
}
}

if (mod.getTermGroupProps) {
termGroupPropsDecorators.push(mod.getTermGroupProps);
}
Expand Down Expand Up @@ -581,3 +594,7 @@ export const middleware: Middleware<{}, HyperState, Dispatch<HyperActions>> = (s
remaining.length ? remaining[0](store)(nextMiddleware(remaining.slice(1)))(action_) : next(action_);
nextMiddleware(middlewares)(action);
};

export function getXtermAddons(): any[] {
return xtermAddons;
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"@types/lodash": "^4.17.0",
"@types/mousetrap": "1.6.15",
"@types/ms": "0.7.34",
"@types/node": "18.19.31",
"@types/node": "^18.19.31",
"@types/plist": "3.0.5",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25",
Expand Down Expand Up @@ -134,7 +134,7 @@
"style-loader": "4.0.0",
"terser": "5.30.3",
"terser-webpack-plugin": "^5.3.10",
"ts-node": "10.9.2",
"ts-node": "^10.9.2",
"typescript": "5.4.5",
"webpack": "5.91.0"
},
Expand Down
8 changes: 8 additions & 0 deletions test/unit/xterm-addons.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Test for xterm addons support from plugins
import test from 'ava';
import {getXtermAddons} from '../../lib/utils/plugins';

test('getXtermAddons returns empty array initially', (t) => {
const addons = getXtermAddons();
t.deepEqual(addons, []);
});
1 change: 1 addition & 0 deletions typings/hyper.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export type hyperPlugin = {
getTabsProps: any;
getTermGroupProps: any;
getTermProps: any;
getXtermAddons: any;
mapHeaderDispatch: any;
mapHyperDispatch: any;
mapHyperTermDispatch: any;
Expand Down