Files
goose/AUTO_NEWLINE_FIX.md
2025-10-31 10:38:36 -04:00

5.5 KiB

🔧 Auto-Insert Newline After #language Trigger

Problem

When the user typed #python (or other language triggers), the code mode would activate and the IDE box would open. However, if the user continued typing, the text would be added to the same line as #python, which would:

  1. Break the trigger pattern (e.g., #pythonmy code is not a valid trigger)
  2. Exit code mode
  3. Render the text as plain text instead of inside the code block

Expected Behavior: After typing #python, the cursor should automatically move to a new line inside the code block, ready for code input.

Solution

Modified the code mode detection useEffect to automatically insert a newline after the language trigger when code mode is activated.

Implementation

// Detect code mode triggers (#language) - can be anywhere in the text
useEffect(() => {
  const languageTriggerRegex = /#(javascript|typescript|python|java|cpp|c|go|rust|ruby|php|swift|kotlin|scala|html|css|json|yaml|sql|bash|shell|powershell|r|matlab|lua|perl|haskell|elixir|clojure|dart|jsx|tsx)(\s|$)/i;
  const match = value.match(languageTriggerRegex);
  
  if (match && !codeMode) {
    // Enter code mode
    const language = match[1].toLowerCase();
    const triggerStart = match.index || 0;
    const triggerLength = match[0].length;
    console.log('💻 CODE MODE ACTIVATED:', language, 'at position', triggerStart);
    
    // Check if there's a newline right after the trigger
    const charAfterTrigger = value[triggerStart + triggerLength];
    const needsNewline = charAfterTrigger !== '\n' && charAfterTrigger !== undefined;
    
    if (needsNewline) {
      // Insert a newline after the trigger to move cursor into code block
      const beforeTrigger = value.slice(0, triggerStart + triggerLength);
      const afterTrigger = value.slice(triggerStart + triggerLength);
      const newValue = beforeTrigger + '\n' + afterTrigger;
      const newCursorPos = triggerStart + triggerLength + 1; // Position after the newline
      
      console.log('💻 CODE MODE: Inserting newline after trigger');
      onChange(newValue, newCursorPos);
      
      // Set cursor position in textarea
      if (hiddenTextareaRef.current) {
        setTimeout(() => {
          hiddenTextareaRef.current?.setSelectionRange(newCursorPos, newCursorPos);
          setCursorPosition(newCursorPos);
        }, 0);
      }
    }
    
    setCodeMode({
      active: true,
      language: language,
      startPos: triggerStart + triggerLength + (needsNewline ? 1 : 0) // Account for the newline
    });
  } else if (!match && codeMode) {
    // Exit code mode if trigger is removed
    console.log('💻 CODE MODE DEACTIVATED');
    setCodeMode(null);
  }
}, [value, codeMode, onChange]);

How It Works

Step-by-Step Flow

  1. User types #python

    • The regex matches the trigger
    • Code mode is not yet active (!codeMode)
  2. Check for existing newline

    • Look at the character immediately after the trigger
    • needsNewline = charAfterTrigger !== '\n' && charAfterTrigger !== undefined
  3. Insert newline if needed

    • Split the value: beforeTrigger + \n + afterTrigger
    • Calculate new cursor position: triggerStart + triggerLength + 1
    • Call onChange with the new value and cursor position
  4. Update cursor in textarea

    • Use setSelectionRange to move the cursor
    • Use setTimeout to ensure it happens after the value update
  5. Set code mode

    • startPos accounts for the inserted newline
    • startPos = triggerStart + triggerLength + 1 (if newline was inserted)

Before the Fix

User types: #python
Value: "#python"
Cursor: at position 7 (after "n")
User continues typing: "def hello():"
Value: "#pythondef hello():"  ❌ Breaks the trigger!

After the Fix

User types: #python
Value: "#python"
Trigger detected → Insert newline
Value: "#python\n"
Cursor: at position 8 (after "\n")
User continues typing: "def hello():"
Value: "#python\ndef hello():"  ✅ Code inside the block!

Edge Cases Handled

  1. Newline already exists: If the user manually types #python\n, we don't insert another newline
  2. End of input: If #python is at the very end with nothing after, we still insert a newline
  3. Inline triggers: Works with text before the trigger (e.g., "some text #python")
  4. Cursor positioning: Ensures the cursor is correctly positioned after the newline

Testing

To verify the fix:

  1. Basic Test:

    • Type #python
    • ✅ Code box opens
    • ✅ Cursor automatically moves to a new line
    • ✅ Continue typing - text appears inside the code block
  2. Inline Test:

    • Type some text #python
    • ✅ Code box opens after "some text"
    • ✅ Cursor moves to new line
    • ✅ Code appears in the block
  3. Manual Newline Test:

    • Type #python then press Enter manually
    • ✅ Only one newline exists (no duplicate)
    • ✅ Code mode still works correctly
  • ui/desktop/src/components/RichChatInput.tsx - Main implementation

Commits

commit fb635770ffa
Auto-insert newline after #language trigger to move cursor into code block

Benefits

  • Better UX: Users don't need to manually press Enter after typing the trigger
  • Prevents errors: Can't accidentally break the trigger by continuing to type
  • Natural flow: Typing feels seamless - trigger activates and you're immediately ready to code
  • Consistent behavior: Works the same way regardless of where the trigger appears in the text