@@ -223,8 +223,11 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
223223 let lastCommandBinding = "" ;
224224 let panelCursorIndex = - 1 ;
225225 let consoleInputBuffer = "" ;
226+ let consoleCursorIndex = 0 ;
226227 let consoleHistoryCursor = - 1 ;
227228 let consoleOutputHistory = [ ] ;
229+ let consoleScrollOffset = 0 ;
230+ let consoleTypingMode = true ;
228231 let consoleCommandHistory = [ ] ;
229232 const keyboardEventTarget = getKeyboardEventTarget ( ) ;
230233 const commandRegistry = createDevConsoleCommandRegistry ( {
@@ -241,8 +244,11 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
241244
242245 function resetConsoleUiState ( ) {
243246 consoleInputBuffer = "" ;
247+ consoleCursorIndex = 0 ;
244248 consoleHistoryCursor = - 1 ;
245249 consoleOutputHistory = [ ] ;
250+ consoleScrollOffset = 0 ;
251+ consoleTypingMode = true ;
246252 }
247253
248254 function normalizeRuntimeDelegationResult ( commandName , execution ) {
@@ -292,6 +298,9 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
292298 const trimCount = consoleOutputHistory . length - MAX_CONSOLE_OUTPUT_LINES ;
293299 consoleOutputHistory . splice ( 0 , trimCount ) ;
294300 }
301+ if ( consoleTypingMode ) {
302+ consoleScrollOffset = 0 ;
303+ }
295304 }
296305
297306 function pushConsoleOutputLines ( lines ) {
@@ -309,6 +318,94 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
309318 consoleCommandHistory . splice ( 0 , trimCount ) ;
310319 }
311320 consoleHistoryCursor = - 1 ;
321+ consoleScrollOffset = 0 ;
322+ }
323+
324+ function clampConsoleCursor ( ) {
325+ consoleCursorIndex = Math . max ( 0 , Math . min ( consoleCursorIndex , consoleInputBuffer . length ) ) ;
326+ }
327+
328+ function setConsoleInputBuffer ( value , moveCursorToEnd = true ) {
329+ consoleInputBuffer = String ( value ?? "" ) ;
330+ if ( consoleInputBuffer . length > MAX_CONSOLE_INPUT_CHARS ) {
331+ consoleInputBuffer = consoleInputBuffer . slice ( 0 , MAX_CONSOLE_INPUT_CHARS ) ;
332+ }
333+
334+ if ( moveCursorToEnd ) {
335+ consoleCursorIndex = consoleInputBuffer . length ;
336+ } else {
337+ clampConsoleCursor ( ) ;
338+ }
339+ }
340+
341+ function insertConsoleText ( text ) {
342+ const chunk = String ( text ?? "" ) ;
343+ if ( ! chunk ) {
344+ return ;
345+ }
346+
347+ const before = consoleInputBuffer . slice ( 0 , consoleCursorIndex ) ;
348+ const after = consoleInputBuffer . slice ( consoleCursorIndex ) ;
349+ const next = `${ before } ${ chunk } ${ after } ` ;
350+ setConsoleInputBuffer ( next , false ) ;
351+ consoleCursorIndex = Math . min ( before . length + chunk . length , consoleInputBuffer . length ) ;
352+ clampConsoleCursor ( ) ;
353+ }
354+
355+ function deleteBeforeCursor ( ) {
356+ if ( consoleCursorIndex <= 0 ) {
357+ return ;
358+ }
359+ const before = consoleInputBuffer . slice ( 0 , consoleCursorIndex - 1 ) ;
360+ const after = consoleInputBuffer . slice ( consoleCursorIndex ) ;
361+ setConsoleInputBuffer ( `${ before } ${ after } ` , false ) ;
362+ consoleCursorIndex = before . length ;
363+ clampConsoleCursor ( ) ;
364+ }
365+
366+ function deleteAtCursor ( ) {
367+ if ( consoleCursorIndex >= consoleInputBuffer . length ) {
368+ return ;
369+ }
370+ const before = consoleInputBuffer . slice ( 0 , consoleCursorIndex ) ;
371+ const after = consoleInputBuffer . slice ( consoleCursorIndex + 1 ) ;
372+ setConsoleInputBuffer ( `${ before } ${ after } ` , false ) ;
373+ consoleCursorIndex = before . length ;
374+ clampConsoleCursor ( ) ;
375+ }
376+
377+ function scrollConsoleHistory ( direction ) {
378+ if ( direction === 0 ) {
379+ return ;
380+ }
381+ const maxOffset = Math . max ( 0 , consoleOutputHistory . length - 1 ) ;
382+ consoleScrollOffset = Math . max ( 0 , Math . min ( maxOffset , consoleScrollOffset + direction ) ) ;
383+ }
384+
385+ function getConsoleHistoryLines ( ) {
386+ const source = consoleOutputHistory . length > 0
387+ ? consoleOutputHistory . slice ( )
388+ : [
389+ "Type a command and press Enter." ,
390+ "Try: help, status, scene.info"
391+ ] ;
392+
393+ if ( consoleTypingMode ) {
394+ return source ;
395+ }
396+
397+ const maxOffset = Math . max ( 0 , source . length - 1 ) ;
398+ const safeOffset = Math . max ( 0 , Math . min ( maxOffset , consoleScrollOffset ) ) ;
399+ if ( safeOffset === 0 ) {
400+ return source ;
401+ }
402+ return source . slice ( 0 , source . length - safeOffset ) ;
403+ }
404+
405+ function getConsoleInputDisplay ( ) {
406+ const before = consoleInputBuffer . slice ( 0 , consoleCursorIndex ) ;
407+ const after = consoleInputBuffer . slice ( consoleCursorIndex ) ;
408+ return `${ before } |${ after } ` ;
312409 }
313410
314411 function appendExecutionToConsole ( command , execution ) {
@@ -351,7 +448,9 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
351448 }
352449 const commandContext = buildRegistryCommandContext ( buildCommandContext ( diagnosticsSnapshot || { } ) ) ;
353450 const execution = executeCommand ( command , commandContext ) ;
354- consoleInputBuffer = "" ;
451+ setConsoleInputBuffer ( "" , true ) ;
452+ consoleTypingMode = true ;
453+ consoleScrollOffset = 0 ;
355454 return execution ;
356455 }
357456
@@ -370,11 +469,13 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
370469 ) ;
371470
372471 if ( consoleHistoryCursor >= consoleCommandHistory . length ) {
373- consoleInputBuffer = "" ;
472+ setConsoleInputBuffer ( "" , true ) ;
374473 return ;
375474 }
376475
377- consoleInputBuffer = consoleCommandHistory [ consoleHistoryCursor ] || "" ;
476+ setConsoleInputBuffer ( consoleCommandHistory [ consoleHistoryCursor ] || "" , true ) ;
477+ consoleTypingMode = true ;
478+ consoleScrollOffset = 0 ;
378479 }
379480
380481 function onConsoleKeyDown ( event ) {
@@ -395,33 +496,67 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
395496 }
396497
397498 if ( code === "Enter" ) {
499+ consoleTypingMode = true ;
398500 submitConsoleInput ( ) ;
399501 event . preventDefault ( ) ;
400502 return ;
401503 }
402504
403505 if ( code === "Backspace" ) {
506+ consoleTypingMode = true ;
507+ deleteBeforeCursor ( ) ;
508+ event . preventDefault ( ) ;
509+ return ;
510+ }
511+
512+ if ( code === "Delete" ) {
513+ consoleTypingMode = true ;
514+ deleteAtCursor ( ) ;
515+ event . preventDefault ( ) ;
516+ return ;
517+ }
518+
519+ if ( code === "Escape" ) {
404520 if ( consoleInputBuffer . length > 0 ) {
405- consoleInputBuffer = consoleInputBuffer . slice ( 0 , - 1 ) ;
521+ setConsoleInputBuffer ( "" , true ) ;
522+ consoleTypingMode = true ;
523+ } else {
524+ consoleTypingMode = ! consoleTypingMode ;
406525 }
407526 event . preventDefault ( ) ;
408527 return ;
409528 }
410529
411- if ( code === "Escape" ) {
412- consoleInputBuffer = "" ;
530+ if ( code === "ArrowLeft" ) {
531+ consoleTypingMode = true ;
532+ consoleCursorIndex = Math . max ( 0 , consoleCursorIndex - 1 ) ;
533+ event . preventDefault ( ) ;
534+ return ;
535+ }
536+
537+ if ( code === "ArrowRight" ) {
538+ consoleTypingMode = true ;
539+ consoleCursorIndex = Math . min ( consoleInputBuffer . length , consoleCursorIndex + 1 ) ;
413540 event . preventDefault ( ) ;
414541 return ;
415542 }
416543
417544 if ( code === "ArrowUp" ) {
418- navigateConsoleHistory ( - 1 ) ;
545+ if ( consoleTypingMode ) {
546+ navigateConsoleHistory ( - 1 ) ;
547+ } else {
548+ scrollConsoleHistory ( 1 ) ;
549+ }
419550 event . preventDefault ( ) ;
420551 return ;
421552 }
422553
423554 if ( code === "ArrowDown" ) {
424- navigateConsoleHistory ( 1 ) ;
555+ if ( consoleTypingMode ) {
556+ navigateConsoleHistory ( 1 ) ;
557+ } else {
558+ scrollConsoleHistory ( - 1 ) ;
559+ }
425560 event . preventDefault ( ) ;
426561 return ;
427562 }
@@ -436,10 +571,13 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
436571 }
437572
438573 if ( consoleInputBuffer . length >= MAX_CONSOLE_INPUT_CHARS ) {
574+ event . preventDefault ( ) ;
439575 return ;
440576 }
441577
442- consoleInputBuffer += key ;
578+ consoleTypingMode = true ;
579+ insertConsoleText ( key ) ;
580+ consoleScrollOffset = 0 ;
443581 event . preventDefault ( ) ;
444582 }
445583
@@ -532,6 +670,9 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
532670 } else {
533671 runtime . showConsole ( ) ;
534672 consoleHistoryCursor = - 1 ;
673+ consoleTypingMode = true ;
674+ consoleScrollOffset = 0 ;
675+ clampConsoleCursor ( ) ;
535676 }
536677 }
537678
@@ -602,16 +743,13 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
602743 const consoleWidth = Number . isFinite ( optionsOverride ?. consoleWidth ) ? optionsOverride . consoleWidth : overlayWidth ;
603744 const consoleHeight = Number . isFinite ( optionsOverride ?. consoleHeight ) ? optionsOverride . consoleHeight : 234 ;
604745
605- const historyLines = consoleOutputHistory . length > 0
606- ? consoleOutputHistory . slice ( )
607- : [
608- "Type a command and press Enter." ,
609- "Try: help, status, scene.info"
610- ] ;
746+ const historyLines = getConsoleHistoryLines ( ) ;
611747
612748 const commandHint = "Shift+` console | Ctrl+Shift+` overlay | Ctrl+Shift+R reload" ;
613- const inputHint = "Enter run | Up/Down history | Esc clear" ;
614- const statusHint = `last: ${ lastCommandBinding || "none" } | status: ${ sanitizeText ( lastCommandExecution ?. status ) || "idle" } | history: ${ consoleCommandHistory . length } ` ;
749+ const inputHint = consoleTypingMode
750+ ? "Typing: Left/Right cursor | Up/Down history | Backspace/Delete | Esc mode"
751+ : "Scroll: Up/Down output | Esc mode" ;
752+ const statusHint = `mode: ${ consoleTypingMode ? "typing" : "scroll" } | scroll: ${ consoleScrollOffset } | last: ${ lastCommandBinding || "none" } | status: ${ sanitizeText ( lastCommandExecution ?. status ) || "idle" } | history: ${ consoleCommandHistory . length } ` ;
615753
616754 drawInteractiveDevConsole ( renderer , {
617755 x : consoleX ,
@@ -620,7 +758,7 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
620758 height : consoleHeight ,
621759 title : "Dev Console (Shift+`)" ,
622760 prompt : ">" ,
623- inputValue : consoleInputBuffer ,
761+ inputValue : getConsoleInputDisplay ( ) ,
624762 outputLines : historyLines ,
625763 footerLines : [ commandHint , inputHint , statusHint ] ,
626764 caretVisible : Math . floor ( Date . now ( ) / 450 ) % 2 === 0
@@ -656,6 +794,9 @@ export function createSampleGameDevConsoleIntegration(options = {}) {
656794 lastCommandExecution,
657795 lastCommandBinding,
658796 consoleInputBuffer,
797+ consoleCursorIndex,
798+ consoleScrollOffset,
799+ consoleTypingMode,
659800 consoleOutputHistory : consoleOutputHistory . slice ( ) ,
660801 consoleCommandHistory : consoleCommandHistory . slice ( ) ,
661802 commandPackCount : commandRegistry . getPackCount ( ) ,
0 commit comments