Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,8 @@ function TextEditor({
)

const textareaStuckRef = useRef(true)
const renderedContentRef = useRef(renderedContent)
renderedContentRef.current = renderedContent

useEffect(() => {
if (!shouldUseCodeRenderer) return
Expand All @@ -772,14 +774,13 @@ function TextEditor({

const updateActiveLineNumber = () => {
const pos = textarea.selectionStart
const textBeforeCursor = renderedContent.substring(0, pos)
const textBeforeCursor = renderedContentRef.current.substring(0, pos)
const nextActiveLineNumber = textBeforeCursor.split('\n').length
setActiveLineNumber((currentLineNumber) =>
currentLineNumber === nextActiveLineNumber ? currentLineNumber : nextActiveLineNumber
)
}

updateActiveLineNumber()
textarea.addEventListener('click', updateActiveLineNumber)
textarea.addEventListener('keyup', updateActiveLineNumber)
textarea.addEventListener('focus', updateActiveLineNumber)
Expand All @@ -789,60 +790,64 @@ function TextEditor({
textarea.removeEventListener('keyup', updateActiveLineNumber)
textarea.removeEventListener('focus', updateActiveLineNumber)
}
}, [renderedContent, shouldUseCodeRenderer])
}, [shouldUseCodeRenderer])

const calculateVisualLinesRef = useRef(() => {})
calculateVisualLinesRef.current = () => {
const preElement = codeEditorRef.current?.querySelector('pre')
if (!(preElement instanceof HTMLElement)) return

const lines = renderedContentRef.current.split('\n')
const newVisualLineHeights: number[] = []

const tempContainer = document.createElement('div')
tempContainer.style.cssText = `
position: absolute;
visibility: hidden;
height: auto;
width: ${preElement.clientWidth}px;
font-family: ${window.getComputedStyle(preElement).fontFamily};
font-size: ${window.getComputedStyle(preElement).fontSize};
line-height: ${CODE_EDITOR_LINE_HEIGHT_PX}px;
padding: 8px;
white-space: pre-wrap;
word-break: break-word;
box-sizing: border-box;
`
document.body.appendChild(tempContainer)

lines.forEach((line) => {
const lineDiv = document.createElement('div')
lineDiv.textContent = line || ' '
tempContainer.appendChild(lineDiv)
const actualHeight = lineDiv.getBoundingClientRect().height
const lineUnits = Math.max(1, Math.ceil(actualHeight / CODE_EDITOR_LINE_HEIGHT_PX))
newVisualLineHeights.push(lineUnits)
tempContainer.removeChild(lineDiv)
})

document.body.removeChild(tempContainer)
setVisualLineHeights((currentVisualLineHeights) =>
areNumberArraysEqual(currentVisualLineHeights, newVisualLineHeights)
? currentVisualLineHeights
: newVisualLineHeights
)
}

useEffect(() => {
if (!shouldUseCodeRenderer || !codeEditorRef.current) return

const calculateVisualLines = () => {
const preElement = codeEditorRef.current?.querySelector('pre')
if (!(preElement instanceof HTMLElement)) return

const lines = renderedContent.split('\n')
const newVisualLineHeights: number[] = []

const tempContainer = document.createElement('div')
tempContainer.style.cssText = `
position: absolute;
visibility: hidden;
height: auto;
width: ${preElement.clientWidth}px;
font-family: ${window.getComputedStyle(preElement).fontFamily};
font-size: ${window.getComputedStyle(preElement).fontSize};
line-height: ${CODE_EDITOR_LINE_HEIGHT_PX}px;
padding: 8px;
white-space: pre-wrap;
word-break: break-word;
box-sizing: border-box;
`
document.body.appendChild(tempContainer)

lines.forEach((line) => {
const lineDiv = document.createElement('div')
lineDiv.textContent = line || ' '
tempContainer.appendChild(lineDiv)
const actualHeight = lineDiv.getBoundingClientRect().height
const lineUnits = Math.max(1, Math.ceil(actualHeight / CODE_EDITOR_LINE_HEIGHT_PX))
newVisualLineHeights.push(lineUnits)
tempContainer.removeChild(lineDiv)
})

document.body.removeChild(tempContainer)
setVisualLineHeights((currentVisualLineHeights) =>
areNumberArraysEqual(currentVisualLineHeights, newVisualLineHeights)
? currentVisualLineHeights
: newVisualLineHeights
)
}

const timeoutId = setTimeout(calculateVisualLines, 50)
const resizeObserver = new ResizeObserver(calculateVisualLines)
const resizeObserver = new ResizeObserver(() => calculateVisualLinesRef.current())
resizeObserver.observe(codeEditorRef.current)

return () => {
clearTimeout(timeoutId)
resizeObserver.disconnect()
}
}, [shouldUseCodeRenderer])

useEffect(() => {
if (!shouldUseCodeRenderer) return
calculateVisualLinesRef.current()
}, [renderedContent, shouldUseCodeRenderer])

const renderCodeLineNumbers = useCallback((): ReactElement[] => {
Expand Down Expand Up @@ -938,7 +943,7 @@ function TextEditor({
if (streamingContent === undefined) {
if (isLoading) return DOCUMENT_SKELETON

if (error) {
if (error && !isInitialized) {
return (
<div className='flex flex-1 items-center justify-center'>
<p className='text-[13px] text-[var(--text-muted)]'>Failed to load file content</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export function GenericResourceContent({ data }: GenericResourceContentProps) {
const bottomRef = useRef<HTMLDivElement>(null)

useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
const el = bottomRef.current
const container = el?.parentElement
if (container) {
container.scrollTop = container.scrollHeight
}
}, [data.entries.length])

if (data.entries.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,14 +397,24 @@ export function UserInput({

useEffect(() => {
if (wasSendingRef.current && !isSending) {
textareaRef.current?.focus()
const active = document.activeElement
const isEditingElsewhere =
active instanceof HTMLTextAreaElement || active instanceof HTMLInputElement
if (!isEditingElsewhere) {
textareaRef.current?.focus()
}
}
Comment on lines +400 to 406
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 contenteditable elements not covered by focus guard

The guard checks HTMLTextAreaElement | HTMLInputElement, but won't catch contenteditable divs (e.g. rich-text editors, ProseMirror, etc.). Currently fine since the file editor uses a <textarea>, but worth expanding if any future editor component uses contenteditable.

Suggested change
const active = document.activeElement
const isEditingElsewhere =
active instanceof HTMLTextAreaElement || active instanceof HTMLInputElement
if (!isEditingElsewhere) {
textareaRef.current?.focus()
}
}
const isEditingElsewhere =
active instanceof HTMLTextAreaElement ||
active instanceof HTMLInputElement ||
(active instanceof HTMLElement && active.isContentEditable)

The same pattern applies to the second effect (initial mount focus) below.

wasSendingRef.current = isSending
}, [isSending, textareaRef])

useEffect(() => {
const raf = window.requestAnimationFrame(() => {
textareaRef.current?.focus()
const active = document.activeElement
const isEditingElsewhere =
active instanceof HTMLTextAreaElement || active instanceof HTMLInputElement
if (!isEditingElsewhere) {
textareaRef.current?.focus()
}
})
return () => window.cancelAnimationFrame(raf)
}, [textareaRef])
Expand Down
Loading