|
@@ -23,6 +23,35 @@ export const Terminal = (props: TerminalProps) => {
|
|
|
let fitAddon: FitAddon
|
|
let fitAddon: FitAddon
|
|
|
let handleResize: () => void
|
|
let handleResize: () => void
|
|
|
const prefersDark = usePrefersDark()
|
|
const prefersDark = usePrefersDark()
|
|
|
|
|
+ const focusTerminal = () => term?.focus()
|
|
|
|
|
+ const copySelection = () => {
|
|
|
|
|
+ if (!term || !term.hasSelection()) return false
|
|
|
|
|
+ const selection = term.getSelection()
|
|
|
|
|
+ if (!selection) return false
|
|
|
|
|
+ const clipboard = navigator.clipboard
|
|
|
|
|
+ if (clipboard?.writeText) {
|
|
|
|
|
+ clipboard.writeText(selection).catch(() => {})
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!document.body) return false
|
|
|
|
|
+ const textarea = document.createElement("textarea")
|
|
|
|
|
+ textarea.value = selection
|
|
|
|
|
+ textarea.setAttribute("readonly", "")
|
|
|
|
|
+ textarea.style.position = "fixed"
|
|
|
|
|
+ textarea.style.opacity = "0"
|
|
|
|
|
+ document.body.appendChild(textarea)
|
|
|
|
|
+ textarea.select()
|
|
|
|
|
+ const copied = document.execCommand("copy")
|
|
|
|
|
+ document.body.removeChild(textarea)
|
|
|
|
|
+ return copied
|
|
|
|
|
+ }
|
|
|
|
|
+ const handlePointerDown = () => {
|
|
|
|
|
+ const activeElement = document.activeElement
|
|
|
|
|
+ if (activeElement instanceof HTMLElement && activeElement !== container) {
|
|
|
|
|
+ activeElement.blur()
|
|
|
|
|
+ }
|
|
|
|
|
+ focusTerminal()
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
onMount(async () => {
|
|
onMount(async () => {
|
|
|
ghostty = await Ghostty.load()
|
|
ghostty = await Ghostty.load()
|
|
@@ -48,8 +77,17 @@ export const Terminal = (props: TerminalProps) => {
|
|
|
ghostty,
|
|
ghostty,
|
|
|
})
|
|
})
|
|
|
term.attachCustomKeyEventHandler((event) => {
|
|
term.attachCustomKeyEventHandler((event) => {
|
|
|
|
|
+ const key = event.key.toLowerCase()
|
|
|
|
|
+ if (key === "c") {
|
|
|
|
|
+ const macCopy = event.metaKey && !event.ctrlKey && !event.altKey
|
|
|
|
|
+ const linuxCopy = event.ctrlKey && event.shiftKey && !event.metaKey
|
|
|
|
|
+ if ((macCopy || linuxCopy) && copySelection()) {
|
|
|
|
|
+ event.preventDefault()
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
// allow for ctrl-` to toggle terminal in parent
|
|
// allow for ctrl-` to toggle terminal in parent
|
|
|
- if (event.ctrlKey && event.key.toLowerCase() === "`") {
|
|
|
|
|
|
|
+ if (event.ctrlKey && key === "`") {
|
|
|
event.preventDefault()
|
|
event.preventDefault()
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|
|
@@ -62,6 +100,8 @@ export const Terminal = (props: TerminalProps) => {
|
|
|
term.loadAddon(fitAddon)
|
|
term.loadAddon(fitAddon)
|
|
|
|
|
|
|
|
term.open(container)
|
|
term.open(container)
|
|
|
|
|
+ container.addEventListener("pointerdown", handlePointerDown)
|
|
|
|
|
+ focusTerminal()
|
|
|
|
|
|
|
|
if (local.pty.buffer) {
|
|
if (local.pty.buffer) {
|
|
|
if (local.pty.rows && local.pty.cols) {
|
|
if (local.pty.rows && local.pty.cols) {
|
|
@@ -75,8 +115,6 @@ export const Terminal = (props: TerminalProps) => {
|
|
|
fitAddon.fit()
|
|
fitAddon.fit()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- container.focus()
|
|
|
|
|
-
|
|
|
|
|
fitAddon.observeResize()
|
|
fitAddon.observeResize()
|
|
|
handleResize = () => fitAddon.fit()
|
|
handleResize = () => fitAddon.fit()
|
|
|
window.addEventListener("resize", handleResize)
|
|
window.addEventListener("resize", handleResize)
|
|
@@ -134,6 +172,7 @@ export const Terminal = (props: TerminalProps) => {
|
|
|
if (handleResize) {
|
|
if (handleResize) {
|
|
|
window.removeEventListener("resize", handleResize)
|
|
window.removeEventListener("resize", handleResize)
|
|
|
}
|
|
}
|
|
|
|
|
+ container.removeEventListener("pointerdown", handlePointerDown)
|
|
|
if (serializeAddon && props.onCleanup) {
|
|
if (serializeAddon && props.onCleanup) {
|
|
|
const buffer = serializeAddon.serialize()
|
|
const buffer = serializeAddon.serialize()
|
|
|
props.onCleanup({
|
|
props.onCleanup({
|