@@ -3,6 +3,39 @@ import { useCallback, useState, useEffect, useMemo, useRef } from 'react'
33
44import { TextAttributes , type ScrollBoxRenderable } from '@opentui/core'
55
6+ const mixColors = ( foreground : string , background : string , alpha = 0.4 ) : string => {
7+ const parseHex = ( hex : string ) => {
8+ const normalized = hex . trim ( ) . replace ( '#' , '' )
9+ const full = normalized . length === 3
10+ ? normalized . split ( '' ) . map ( ( ch ) => ch + ch ) . join ( '' )
11+ : normalized
12+ const value = parseInt ( full , 16 )
13+ return {
14+ r : ( value >> 16 ) & 0xff ,
15+ g : ( value >> 8 ) & 0xff ,
16+ b : value & 0xff ,
17+ }
18+ }
19+
20+ const clamp = ( value : number ) => Math . max ( 0 , Math . min ( 255 , Math . round ( value ) ) )
21+
22+ try {
23+ const fg = parseHex ( foreground )
24+ const bg = parseHex ( background )
25+
26+ const blend = {
27+ r : clamp ( alpha * fg . r + ( 1 - alpha ) * bg . r ) ,
28+ g : clamp ( alpha * fg . g + ( 1 - alpha ) * bg . g ) ,
29+ b : clamp ( alpha * fg . b + ( 1 - alpha ) * bg . b ) ,
30+ }
31+
32+ const toHex = ( value : number ) => value . toString ( 16 ) . padStart ( 2 , '0' )
33+ return `#${ toHex ( blend . r ) } ${ toHex ( blend . g ) } ${ toHex ( blend . b ) } `
34+ } catch {
35+ return foreground
36+ }
37+ }
38+
639
740// Helper functions for text manipulation
841function findLineStart ( text : string , cursor : number ) : number {
@@ -53,7 +86,7 @@ function findNextWordBoundary(text: string, cursor: number): number {
5386 return pos
5487}
5588
56- const CURSOR_CHAR = '\u2009 '
89+ const CURSOR_CHAR = '▏ '
5790
5891interface MultilineInputProps {
5992 value : string
@@ -396,17 +429,20 @@ export function MultilineInput({
396429 // Calculate display with cursor
397430 const displayValue = value || placeholder
398431 const isPlaceholder = ! value && placeholder
399- const showCursor = focused && ! isPlaceholder
432+ const showCursor = focused
400433 const beforeCursor = showCursor ? displayValue . slice ( 0 , cursorPosition ) : ''
401434 const afterCursor = showCursor ? displayValue . slice ( cursorPosition ) : ''
402- const displayText = showCursor
403- ? ` ${ beforeCursor } ${ CURSOR_CHAR } ${ afterCursor } `
404- : displayValue
435+ const activeChar = afterCursor . charAt ( 0 ) || ' '
436+ const highlightBg = mixColors ( theme . cursor , isPlaceholder ? theme . inputBg : theme . inputFocusedBg , 0.4 )
437+ const shouldHighlight = showCursor && ! isPlaceholder && cursorPosition > 0 && cursorPosition < displayValue . length
405438
406- // Memoize height calculation to avoid expensive computation on every render
407439 const height = useMemo ( ( ) => {
408440 const maxCharsPerLine = Math . max ( 1 , width - 4 )
409- const contentForHeight = showCursor ? displayText : displayValue
441+ const contentForHeight = showCursor
442+ ? shouldHighlight
443+ ? displayValue
444+ : `${ displayValue . slice ( 0 , cursorPosition ) } ${ CURSOR_CHAR } ${ displayValue . slice ( cursorPosition ) } `
445+ : displayValue
410446 const lines = contentForHeight . split ( '\n' )
411447 let totalLineCount = 0
412448 for ( const line of lines ) {
@@ -418,7 +454,7 @@ export function MultilineInput({
418454 }
419455 }
420456 return Math . max ( 1 , Math . min ( totalLineCount , maxHeight ) )
421- } , [ displayValue , displayText , showCursor , width , maxHeight ] )
457+ } , [ displayValue , cursorPosition , showCursor , width , maxHeight ] )
422458
423459 return (
424460 < scrollbox
@@ -460,17 +496,23 @@ export function MultilineInput({
460496 { showCursor ? (
461497 < >
462498 { beforeCursor }
463- < span
464- fg = { theme . cursor }
465- bg = { theme . cursor }
466- attributes = { TextAttributes . BOLD }
467- >
468- { CURSOR_CHAR }
469- </ span >
470- { afterCursor }
499+ { shouldHighlight ? (
500+ < span fg = { theme . inputFocusedFg } bg = { highlightBg } >
501+ { activeChar === ' ' ? '\u00a0' : activeChar }
502+ </ span >
503+ ) : (
504+ < span fg = { theme . cursor } attributes = { TextAttributes . BOLD } >
505+ { CURSOR_CHAR }
506+ </ span >
507+ ) }
508+ { shouldHighlight
509+ ? afterCursor . length > 0
510+ ? afterCursor . slice ( 1 )
511+ : ''
512+ : afterCursor || ' ' }
471513 </ >
472514 ) : (
473- displayText
515+ displayValue
474516 ) }
475517 </ text >
476518 </ scrollbox >
0 commit comments