Skip to content

Commit

Permalink
Cache Terminal instances and send SIGKILL on closing
Browse files Browse the repository at this point in the history
  • Loading branch information
elanzini committed Apr 1, 2023
1 parent 865f052 commit cd908aa
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 53 deletions.
98 changes: 61 additions & 37 deletions src/components/terminal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useCallback } from 'react'
import React, { useEffect, useRef, useState, useCallback } from 'react'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { WebLinksAddon } from 'xterm-addon-web-links'
Expand All @@ -10,9 +10,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faTimes, faPlus, faTerminal } from '@fortawesome/free-solid-svg-icons'
import { throttleCallback } from './componentUtils'

export function XTermComponent({ height, id }: { height: number; id: number }) {
export function XTermComponent({
height,
id,
terminalInstance,
}: {
height: number
id: number
terminalInstance: Terminal
}) {
const terminalRef = useRef<HTMLDivElement>(null)
const terminal = useRef<Terminal | null>(null)
const terminal = useRef<Terminal>(terminalInstance)
const fitAddon = useRef<FitAddon>(new FitAddon())
const webLinksAddon = useRef<WebLinksAddon>(
new WebLinksAddon((event: MouseEvent, url: string) => {
Expand All @@ -26,49 +34,45 @@ export function XTermComponent({ height, id }: { height: number; id: number }) {

const handleIncomingData = useCallback(
(e: { id: number }, data: any) => {
if (e.id === id && terminal.current) {
if (e.id === id) {
terminal.current.write(data)
}
},
[id]
)

useEffect(() => {
terminal.current = new Terminal({
theme: {
background: '#1e1e1e',
foreground: '#f1f1f1',
},
})
terminal.current.onResize((size: { cols: number; rows: number }) => {
connector.terminalResize(id, size)
})
const resizeDisposable = terminal.current.onResize(
(size: { cols: number; rows: number }) => {
connector.terminalResize(id, size)
}
)

terminal.current.loadAddon(fitAddon.current)
terminal.current.loadAddon(webLinksAddon.current)

if (terminalRef.current) {
terminal.current.open(terminalRef.current)
// Send a single newline character to the terminal when it is first opened
connector.terminalInto(id, '\n')
}

terminal.current.onData((e) => {
const dataDisposable = terminal.current.onData((e) => {
connector.terminalInto(id, e)
})

connector.registerIncData(id, handleIncomingData)
const wrappedCallback = connector.registerIncData(
id,
handleIncomingData
)

// Make the terminal's size and geometry fit the size of #terminal-container
fitAddon.current.fit()

return () => {
if (terminal.current) {
terminal.current.dispose()
}
connector.deregisterIncData(id, handleIncomingData)
dataDisposable.dispose()
resizeDisposable.dispose()
connector.deregisterIncData(id, wrappedCallback)
}
}, [terminalRef, id, handleIncomingData])
}, [id, terminalInstance])

useEffect(() => {
if (terminal.current != null) {
Expand Down Expand Up @@ -105,6 +109,7 @@ export function XTermComponent({ height, id }: { height: number; id: number }) {

type TerminalWrapper = {
id: number
terminalInstance: Terminal
}

type TerminalTabsProps = {
Expand Down Expand Up @@ -161,17 +166,36 @@ export const BottomTerminal: React.FC = () => {
(state: FullState) => state.global.rootPath
)

const [terminals, setTerminals] = React.useState([{ id: 0 }])
const [activeTerminal, setActiveTerminal] = React.useState(terminals[0])

const [terminalOpen, setTerminalOpen] = React.useState(false)
const [terminals, setTerminals] = React.useState<TerminalWrapper[]>([
{
id: 0,
terminalInstance: new Terminal({
theme: {
background: '#1e1e1e',
foreground: '#f1f1f1',
},
}),
},
])
const [activeTerminal, setActiveTerminal] = React.useState<TerminalWrapper>(
terminals[0]
)

const addTerminal = () => {
const addTerminal = async () => {
if (terminals.length >= 10) {
return
}
connector.createNewTerminal()
const newTerminal = { id: Math.max(...terminals.map((t) => t.id)) + 1 }
const newTerminalId = await connector.createNewTerminal()
const newTerminalInstance = new Terminal({
theme: {
background: '#1e1e1e',
foreground: '#f1f1f1',
},
})
const newTerminal: TerminalWrapper = {
id: newTerminalId,
terminalInstance: newTerminalInstance,
}
setTerminals([...terminals, newTerminal])
setActiveTerminal(newTerminal)
}
Expand All @@ -183,6 +207,10 @@ export const BottomTerminal: React.FC = () => {
(terminal) => terminal.id !== terminalToRemove.id
)
)
// Send SIGKILL to the terminal
connector.sendSigKill(terminalToRemove.id)
// Dispose the Terminal object
terminalToRemove.terminalInstance.dispose()
if (activeTerminal.id === terminalToRemove.id) {
setActiveTerminal(terminals[0] || {})
}
Expand All @@ -193,15 +221,8 @@ export const BottomTerminal: React.FC = () => {

const switchTerminal = (terminal: TerminalWrapper) => {
setActiveTerminal(terminal)
connector.sendSigCont(terminal.id)
}

useEffect(() => {
if (terminalOpenSelector) {
setTerminalOpen(true)
}
}, [terminalOpenSelector])

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === '`' && event.ctrlKey) {
Expand Down Expand Up @@ -251,7 +272,7 @@ export const BottomTerminal: React.FC = () => {

return (
<>
{terminalOpen ? (
{terminalOpenSelector ? (
<div className="terminalOuterContainer">
<div
className="dragHandle"
Expand Down Expand Up @@ -297,6 +318,9 @@ export const BottomTerminal: React.FC = () => {
terminalInnerContainerHeight
}
id={terminal.id}
terminalInstance={
terminal.terminalInstance
}
/>
)
)}
Expand Down
2 changes: 1 addition & 1 deletion src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,7 @@ const createWindow = () => {
setupTerminal(main_window, terminalCounter)
}
terminalCounter++
return terminalCounter
return terminalCounter - 1
}

ipcMain.handle('create-new-terminal', (event) => {
Expand Down
7 changes: 5 additions & 2 deletions src/main/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export function setupTerminal(
env: filteredEnv,
})
ptyProcess = res
if (newTerminalId === 0) {
ptyProcess.write('\n')
}
break
} catch (e) {
// ignore errors
Expand All @@ -54,8 +57,8 @@ export function setupTerminal(
ptyProcess.resize(size.cols, size.rows)
})

ipcMain.handle(`terminal-sigcont-${newTerminalId}`, (event) => {
ptyProcess.kill('SIGCONT')
ipcMain.handle(`terminal-sigkill-${newTerminalId}`, (event) => {
ptyProcess.kill('SIGKILL')
})

ptyProcess.on('data', (data: any) => {
Expand Down
23 changes: 10 additions & 13 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export const clientPreloads = () => {
}
}
const info = getPlatformInfo()

const electronConnector = {
PLATFORM_DELIMITER: info.PLATFORM_DELIMITER,
PLATFORM_META_KEY: info.PLATFORM_META_KEY,
Expand Down Expand Up @@ -182,19 +183,15 @@ const electronConnector = {
ipcRenderer.on('openRemotePopup', callback),

registerIncData(id: number, callback: (event: any, data: any) => void) {
ipcRenderer.on(`terminal-incData-${id}`, (event: any, data: any) => {
const wrappedCallback = (event: any, data: any) => {
callback({ ...event, id }, data)
})
}
ipcRenderer.on(`terminal-incData-${id}`, wrappedCallback)
return wrappedCallback
},

deregisterIncData(id: number, callback: (event: any, data: any) => void) {
ipcRenderer.removeListener(
`terminal-incData-${id}`,
(event: any, data: any) => {
if (event.id === id) {
callback(event, data)
}
}
)
ipcRenderer.removeListener(`terminal-incData-${id}`, callback)
},
terminalInto: (terminalId: number, data: any) =>
ipcRenderer.invoke(`terminal-into-${terminalId}`, data),
Expand All @@ -204,8 +201,8 @@ const electronConnector = {
const terminalId = await ipcRenderer.invoke('create-new-terminal')
return terminalId
},
sendSigCont(terminalId: number) {
ipcRenderer.invoke(`terminal-sigcont-${terminalId}`)
sendSigKill(terminalId: number) {
ipcRenderer.invoke(`terminal-sigkill-${terminalId}`)
},
terminalResize: (terminalId: number, data: any) =>
ipcRenderer.invoke(`terminal-resize-${terminalId}`, data),
Expand Down Expand Up @@ -433,7 +430,7 @@ const electronConnector = {
},
registerCloseErrors(callback: Callback) {
ipcRenderer.on('closeErrors', callback)
}
},
}

contextBridge.exposeInMainWorld('connector', electronConnector)
Expand Down

0 comments on commit cd908aa

Please sign in to comment.