Skip to content

Commit 91d6600

Browse files
author
DavidQ
committed
DEV_CONSOLE_ARROW_KEYS_EDIT_AND_SCROLL_PATCH
- Added cursor editing - Added scroll mode - Improved keyboard handling
1 parent 7da406f commit 91d6600

3 files changed

Lines changed: 184 additions & 36 deletions

File tree

docs/dev/CODEX_COMMANDS.md

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@ MODEL: GPT-5.4-codex
22
REASONING: high
33

44
COMMAND:
5-
Execute full PLAN+BUILD+APPLY.
5+
Patch dev console input system.
66

7-
- Create registry
8-
- Create command packs
9-
- Wire into devConsoleIntegration.js
10-
- Implement help system
11-
- Ensure output contract
12-
- Do not modify engine core
13-
- Package final result to:
14-
<project>/tmp/PLAN_BUILD_APPLY_PR_DEV_CONSOLE_COMMAND_PACKS_delta.zip
7+
Modify:
8+
tools/dev/devConsoleIntegration.js
159

16-
Report:
17-
- files created
18-
- files updated
19-
- commands available
20-
- validation results
10+
Add:
11+
- cursor-based input editing (left/right arrows)
12+
- backspace/delete support
13+
- command history (up/down)
14+
- scroll mode when not typing
15+
- preventDefault for arrow keys
16+
17+
Rules:
18+
- Do NOT modify engine core
19+
- Keep combo keys unchanged
20+
- Keep logic isolated to this file only
21+
22+
Validation:
23+
- arrows move cursor
24+
- backspace/delete works
25+
- history works
26+
- scroll works
27+
- no browser scrolling

docs/dev/COMMIT_COMMENT.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
PLAN_BUILD_APPLY_PR_DEV_CONSOLE_COMMAND_PACKS
1+
DEV_CONSOLE_ARROW_KEYS_EDIT_AND_SCROLL_PATCH
22

3-
- Implemented full command pack system
4-
- Wired registry and packs
5-
- Applied integration
3+
- Added cursor editing
4+
- Added scroll mode
5+
- Improved keyboard handling

tools/dev/devConsoleIntegration.js

Lines changed: 159 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)