Skip to content
Merged
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
@@ -1,3 +1,3 @@
import './configs'

import { Editor as PrimitiveEditor } from '@monaco-editor/react'
Expand Down Expand Up @@ -1320,6 +1320,46 @@
coexistenceRef.current?.setActive(inlineCompletionsActive)
}, [inlineCompletionsActive, editorInstanceId])

// AI inline-completion idle re-trigger.
//
// Monaco only auto-triggers inline completions on a content change and renders
// just the latest call's result, so a request can complete without ever
// painting (a late/superseded result is silently dropped) — after which
// nothing re-requests until the next keystroke, and the suggestion appears to
// "give up". This re-arms it: once the user has been idle for 2s with AI on and
// no ghost text currently visible, we explicitly re-trigger inline suggest so
// the editor always eventually offers something for a settled cursor. The 2s
// window keeps this from firing needless requests during active editing; it
// fires at most once per idle period (triggering does not change content, so
// the timer is not re-armed by its own action).
useEffect(() => {
if (!inlineCompletionsActive) return
const editor = editorRef.current
if (!editor) return

const IDLE_MS = 2000
let idleTimer: ReturnType<typeof setTimeout> | undefined

const scheduleIdleRetrigger = () => {
if (idleTimer) clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
if (!editor.hasTextFocus()) return
const model = editor.getModel()
if (!model || model.getValueLength() === 0) return
// Skip if a ghost is already showing (avoid a redundant request).
const dom = editor.getDomNode()
if (dom?.querySelector('.ghost-text-decoration, .ghost-text, [class*="ghost-text"]')) return
editor.trigger('openplc-ai-idle', 'editor.action.inlineSuggest.trigger', {})
}, IDLE_MS)
}

const changeDisposable = editor.onDidChangeModelContent(scheduleIdleRetrigger)
return () => {
if (idleTimer) clearTimeout(idleTimer)
changeDisposable.dispose()
}
Comment on lines +1335 to +1360

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Programmatic model edits also re-arm the idle re-trigger.

onDidChangeModelContent fires for programmatic writes too (model.setValue, executeEdits), not just user keystrokes. This file performs several such writes — diff-review hunk undo (executeEdits('ai-diff-undo-hunk', ...)), AI chat/tool updates (executeEdits('ai-tool-update', ...)), and external file-reload sync — all guarded elsewhere by isSyncingModelRef to suppress handleWriteInPou's store-sync side effects, but this new listener isn't gated on that flag. If the editor still has focus when one of those programmatic writes lands, this effect will schedule (and 2s later fire) an inline-suggest re-trigger that wasn't caused by user typing.

🐛 Proposed fix
-    const changeDisposable = editor.onDidChangeModelContent(scheduleIdleRetrigger)
+    const changeDisposable = editor.onDidChangeModelContent(() => {
+      if (isSyncingModelRef.current) return
+      scheduleIdleRetrigger()
+    })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!inlineCompletionsActive) return
const editor = editorRef.current
if (!editor) return
const IDLE_MS = 2000
let idleTimer: ReturnType<typeof setTimeout> | undefined
const scheduleIdleRetrigger = () => {
if (idleTimer) clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
if (!editor.hasTextFocus()) return
const model = editor.getModel()
if (!model || model.getValueLength() === 0) return
// Skip if a ghost is already showing (avoid a redundant request).
const dom = editor.getDomNode()
if (dom?.querySelector('.ghost-text-decoration, .ghost-text, [class*="ghost-text"]')) return
editor.trigger('openplc-ai-idle', 'editor.action.inlineSuggest.trigger', {})
}, IDLE_MS)
}
const changeDisposable = editor.onDidChangeModelContent(scheduleIdleRetrigger)
return () => {
if (idleTimer) clearTimeout(idleTimer)
changeDisposable.dispose()
}
useEffect(() => {
if (!inlineCompletionsActive) return
const editor = editorRef.current
if (!editor) return
const IDLE_MS = 2000
let idleTimer: ReturnType<typeof setTimeout> | undefined
const scheduleIdleRetrigger = () => {
if (idleTimer) clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
if (!editor.hasTextFocus()) return
const model = editor.getModel()
if (!model || model.getValueLength() === 0) return
// Skip if a ghost is already showing (avoid a redundant request).
const dom = editor.getDomNode()
if (dom?.querySelector('.ghost-text-decoration, .ghost-text, [class*="ghost-text"]')) return
editor.trigger('openplc-ai-idle', 'editor.action.inlineSuggest.trigger', {})
}, IDLE_MS)
}
const changeDisposable = editor.onDidChangeModelContent(() => {
if (isSyncingModelRef.current) return
scheduleIdleRetrigger()
})
return () => {
if (idleTimer) clearTimeout(idleTimer)
changeDisposable.dispose()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/components/_features/`[workspace]/editor/monaco/index.tsx around
lines 1335 - 1360, Programmatic model updates are incorrectly re-arming the idle
inline-suggest retrigger in the Monaco editor. Update the `useEffect` that
registers `editor.onDidChangeModelContent(scheduleIdleRetrigger)` so it ignores
changes caused by internal writes, using the existing `isSyncingModelRef` guard
(or equivalent) before scheduling the timer. Make sure the guard covers
programmatic `executeEdits`/`setValue` paths used by the diff undo, AI tool
updates, and file reload sync, while still allowing real user typing to trigger
`editor.action.inlineSuggest.trigger`.

}, [inlineCompletionsActive, editorInstanceId])

// -----------------------------------------------------------------------
// Drag-and-drop
// -----------------------------------------------------------------------
Expand Down
Loading