Skip to content
Closed
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
8 changes: 5 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@
"@vercel/analytics": "^1.5.0",
"@vercel/otel": "^2.1.2",
"@vercel/speed-insights": "^1.2.0",
"@xterm/xterm": "^6.0.0",
"cheerio": "^1.0.0",
"chrono-node": "^2.8.4",
"class-variance-authority": "^0.7.1",
Expand All @@ -103,6 +102,7 @@
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.2",
"fast-xml-parser": "^5.3.5",
"ghostty-web": "^0.4.0",
Comment thread
matthewlouisbrockman marked this conversation as resolved.
"immer": "^10.1.1",
"input-otp": "^1.4.2",
"micromatch": "^4.0.8",
Expand Down Expand Up @@ -131,6 +131,7 @@
"server-only": "^0.0.1",
"shiki": "3.2.1",
"sonner": "^2.0.7",
"strip-ansi": "^7.1.2",
"superjson": "^2.2.5",
"tailwind-merge": "^3.4.0",
"tw-animate-css": "^1.3.6",
Expand Down
4 changes: 3 additions & 1 deletion src/features/dashboard/terminal/dashboard-terminal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { type CommandHandle, type Sandbox } from 'e2b'
import type { CommandHandle, Sandbox } from 'e2b'
import { useCallback, useEffect, useRef, useState } from 'react'
import type { SandboxManagementAuth } from '@/core/shared/sandbox-management-auth'
import {
Expand Down Expand Up @@ -200,6 +200,7 @@ export default function DashboardTerminal({
appendOutput,
copyTerminalText,
focusTerminal,
pasteTerminalText,
resetTerminal,
resizeTerminal,
terminalContainerRef,
Expand Down Expand Up @@ -597,6 +598,7 @@ export default function DashboardTerminal({
template={sandboxScoped ? undefined : template}
terminalContainerRef={terminalContainerRef}
onFocusTerminal={focusTerminal}
onPasteTerminalText={pasteTerminalText}
onCopyTerminalText={() => void copyTerminalText()}
onRestartTerminal={restartTerminal}
/>
Expand Down
5 changes: 4 additions & 1 deletion src/features/dashboard/terminal/terminal-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RefObject } from 'react'
import type { ClipboardEvent, RefObject } from 'react'
import { IconButton } from '@/ui/primitives/icon-button'
import { CopyIcon, RefreshIcon, TerminalIcon } from '@/ui/primitives/icons'

Expand All @@ -9,6 +9,7 @@ interface TerminalPanelProps {
restartLabel: string
terminalContainerRef: RefObject<HTMLDivElement | null>
onFocusTerminal: () => void
onPasteTerminalText: (event: ClipboardEvent<HTMLDivElement>) => void
onCopyTerminalText: () => void
onRestartTerminal: () => void
}
Expand All @@ -20,6 +21,7 @@ export default function TerminalPanel({
restartLabel,
terminalContainerRef,
onFocusTerminal,
onPasteTerminalText,
onCopyTerminalText,
onRestartTerminal,
}: TerminalPanelProps) {
Expand All @@ -41,6 +43,7 @@ export default function TerminalPanel({
aria-label="Terminal"
className="min-h-0 flex-1 cursor-text overflow-hidden bg-black p-3"
onMouseDown={onFocusTerminal}
onPasteCapture={onPasteTerminalText}
/>
</section>
)
Expand Down
26 changes: 26 additions & 0 deletions src/features/dashboard/terminal/terminal-paste.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import stripAnsi from 'strip-ansi'

const DELETE = 0x7f

const isAllowedPasteControl = (code: number) =>
code === 0x09 || code === 0x0a || code === 0x0d

export function sanitizeTerminalPaste(value: string) {
let sanitized = ''

for (const char of stripAnsi(value)) {
const code = char.charCodeAt(0)

if ((code < 0x20 && !isAllowedPasteControl(code)) || code === DELETE) {
continue
}

if (code >= 0x80 && code <= 0x9f) {
continue
}

sanitized += char
}

return sanitized
}
69 changes: 16 additions & 53 deletions src/features/dashboard/terminal/terminal-size.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { Terminal as XTerm } from '@xterm/xterm'
import { DEFAULT_COLS, DEFAULT_PANEL_HEIGHT, DEFAULT_ROWS } from './constants'

const MIN_TERMINAL_COLS = 40
Expand All @@ -7,58 +6,27 @@ const TERMINAL_PADDING_PX = 24
const TERMINAL_SCROLLBAR_GUTTER_PX = 44
const DEFAULT_CELL_WIDTH_PX = 8
const DEFAULT_CELL_HEIGHT_PX = 20
const MIN_CELL_WIDTH_PX = 4
const MAX_CELL_WIDTH_PX = 16
const MIN_CELL_HEIGHT_PX = 8
const MAX_CELL_HEIGHT_PX = 40

function getElementSize(element: Element | null) {
if (!element) return undefined

const rect = element.getBoundingClientRect()
if (!rect.width || !rect.height) return undefined

return rect
}

function getMeasuredCellSize(terminal: XTerm | null) {
const measureElement = terminal?.element?.querySelector(
'.xterm-char-measure-element'
)
const rowElement = terminal?.element?.querySelector('.xterm-rows > div')
const measuredCharSize = getElementSize(measureElement ?? null)
const rowSize = getElementSize(rowElement ?? null)

if (!measuredCharSize && !rowSize) return undefined

const measuredWidth = measuredCharSize?.width
const measuredHeight = rowSize?.height ?? measuredCharSize?.height

return {
width:
measuredWidth &&
measuredWidth >= MIN_CELL_WIDTH_PX &&
measuredWidth <= MAX_CELL_WIDTH_PX
? measuredWidth
: undefined,
height:
measuredHeight &&
measuredHeight >= MIN_CELL_HEIGHT_PX &&
measuredHeight <= MAX_CELL_HEIGHT_PX
? measuredHeight
: undefined,
}
type TerminalLike = {
cols: number
rows: number
}

export function calculateTerminalSize(
container: HTMLDivElement | null,
terminal: XTerm | null
terminal: TerminalLike | null
) {
if (terminal) {
return {
cols: terminal.cols,
rows: terminal.rows,
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

if (!container) {
return { cols: DEFAULT_COLS, rows: DEFAULT_ROWS }
}

const measuredCellSize = getMeasuredCellSize(terminal)
const containerRect = container.getBoundingClientRect()
const containerWidth =
container.clientWidth || containerRect.width || window.innerWidth
Expand All @@ -67,20 +35,15 @@ export function calculateTerminalSize(
const availableWidth =
containerWidth - TERMINAL_PADDING_PX - TERMINAL_SCROLLBAR_GUTTER_PX
const availableHeight = containerHeight - TERMINAL_PADDING_PX
const cellWidth = Math.max(
measuredCellSize?.width ?? DEFAULT_CELL_WIDTH_PX,
1
)
const cellHeight = Math.max(
measuredCellSize?.height ?? DEFAULT_CELL_HEIGHT_PX,
1
)

return {
cols: Math.max(MIN_TERMINAL_COLS, Math.floor(availableWidth / cellWidth)),
cols: Math.max(
MIN_TERMINAL_COLS,
Math.floor(availableWidth / DEFAULT_CELL_WIDTH_PX)
),
rows: Math.max(
MIN_TERMINAL_ROWS,
Math.floor(availableHeight / cellHeight) - 1
Math.floor(availableHeight / DEFAULT_CELL_HEIGHT_PX) - 1
),
}
}
Loading
Loading