mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: prevent race condition when switching modes rapidly with ctrl+.
- Added debouncing (150ms) to mode switching functions to prevent rapid consecutive switches - Added isModeSwitching state flag to prevent concurrent mode switches in both UI and backend - Updated tests to account for debounce delay - Fixes issue where rapidly pressing ctrl+. could assign wrong model to mode Fixes #6764
This commit is contained in:
parent
2b647ed9a1
commit
6884871ffc
3 changed files with 73 additions and 26 deletions
|
|
@ -112,6 +112,7 @@ export class ClineProvider
|
|||
protected mcpHub?: McpHub // Change from private to protected
|
||||
private marketplaceManager: MarketplaceManager
|
||||
private mdmService?: MdmService
|
||||
private isModeSwitching = false
|
||||
|
||||
public isViewLaunched = false
|
||||
public settingsImportedAt?: number
|
||||
|
|
@ -956,6 +957,23 @@ export class ClineProvider
|
|||
* @param newMode The mode to switch to
|
||||
*/
|
||||
public async handleModeSwitch(newMode: Mode) {
|
||||
// Prevent concurrent mode switches
|
||||
if (this.isModeSwitching) {
|
||||
this.log(`Mode switch already in progress, ignoring switch to ${newMode}`)
|
||||
return
|
||||
}
|
||||
|
||||
this.isModeSwitching = true
|
||||
|
||||
try {
|
||||
await this.performModeSwitch(newMode)
|
||||
} finally {
|
||||
// Always reset the flag, even if an error occurs
|
||||
this.isModeSwitching = false
|
||||
}
|
||||
}
|
||||
|
||||
private async performModeSwitch(newMode: Mode) {
|
||||
const cline = this.getCurrentCline()
|
||||
|
||||
if (cline) {
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
const [showCheckpointWarning, setShowCheckpointWarning] = useState<boolean>(false)
|
||||
const [isCondensing, setIsCondensing] = useState<boolean>(false)
|
||||
const [showAnnouncementModal, setShowAnnouncementModal] = useState(false)
|
||||
const [isModeSwitching, setIsModeSwitching] = useState<boolean>(false)
|
||||
const everVisibleMessagesTsRef = useRef<LRUCache<number, boolean>>(
|
||||
new LRUCache({
|
||||
max: 100,
|
||||
|
|
@ -1456,6 +1457,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
// Function to switch to a specific mode
|
||||
const switchToMode = useCallback(
|
||||
(modeSlug: string): void => {
|
||||
// Prevent concurrent mode switches
|
||||
if (isModeSwitching) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set flag to prevent concurrent switches
|
||||
setIsModeSwitching(true)
|
||||
|
||||
// Update local state and notify extension to sync mode change
|
||||
setMode(modeSlug)
|
||||
|
||||
|
|
@ -1464,8 +1473,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
type: "mode",
|
||||
text: modeSlug,
|
||||
})
|
||||
|
||||
// Reset the flag after a short delay to allow the mode switch to complete
|
||||
setTimeout(() => {
|
||||
setIsModeSwitching(false)
|
||||
}, 300)
|
||||
},
|
||||
[setMode],
|
||||
[setMode, isModeSwitching],
|
||||
)
|
||||
|
||||
const handleSuggestionClickInRow = useCallback(
|
||||
|
|
@ -1714,23 +1728,31 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
tSettings,
|
||||
])
|
||||
|
||||
// Function to handle mode switching
|
||||
const switchToNextMode = useCallback(() => {
|
||||
const allModes = getAllModes(customModes)
|
||||
const currentModeIndex = allModes.findIndex((m) => m.slug === mode)
|
||||
const nextModeIndex = (currentModeIndex + 1) % allModes.length
|
||||
// Update local state and notify extension to sync mode change
|
||||
switchToMode(allModes[nextModeIndex].slug)
|
||||
}, [mode, customModes, switchToMode])
|
||||
// Function to handle mode switching with debouncing
|
||||
const switchToNextMode = useMemo(
|
||||
() =>
|
||||
debounce(() => {
|
||||
const allModes = getAllModes(customModes)
|
||||
const currentModeIndex = allModes.findIndex((m) => m.slug === mode)
|
||||
const nextModeIndex = (currentModeIndex + 1) % allModes.length
|
||||
// Update local state and notify extension to sync mode change
|
||||
switchToMode(allModes[nextModeIndex].slug)
|
||||
}, 150),
|
||||
[mode, customModes, switchToMode],
|
||||
)
|
||||
|
||||
// Function to handle switching to previous mode
|
||||
const switchToPreviousMode = useCallback(() => {
|
||||
const allModes = getAllModes(customModes)
|
||||
const currentModeIndex = allModes.findIndex((m) => m.slug === mode)
|
||||
const previousModeIndex = (currentModeIndex - 1 + allModes.length) % allModes.length
|
||||
// Update local state and notify extension to sync mode change
|
||||
switchToMode(allModes[previousModeIndex].slug)
|
||||
}, [mode, customModes, switchToMode])
|
||||
// Function to handle switching to previous mode with debouncing
|
||||
const switchToPreviousMode = useMemo(
|
||||
() =>
|
||||
debounce(() => {
|
||||
const allModes = getAllModes(customModes)
|
||||
const currentModeIndex = allModes.findIndex((m) => m.slug === mode)
|
||||
const previousModeIndex = (currentModeIndex - 1 + allModes.length) % allModes.length
|
||||
// Update local state and notify extension to sync mode change
|
||||
switchToMode(allModes[previousModeIndex].slug)
|
||||
}, 150),
|
||||
[mode, customModes, switchToMode],
|
||||
)
|
||||
|
||||
// Add keyboard event handler
|
||||
const handleKeyDown = useCallback(
|
||||
|
|
@ -1752,13 +1774,20 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
[switchToNextMode, switchToPreviousMode],
|
||||
)
|
||||
|
||||
// Add event listener
|
||||
// Add event listener and cleanup debounced functions
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyDown)
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown)
|
||||
// Cancel any pending debounced calls when unmounting
|
||||
if (typeof (switchToNextMode as any).cancel === "function") {
|
||||
;(switchToNextMode as any).cancel()
|
||||
}
|
||||
if (typeof (switchToPreviousMode as any).cancel === "function") {
|
||||
;(switchToPreviousMode as any).cancel()
|
||||
}
|
||||
}
|
||||
}, [handleKeyDown])
|
||||
}, [handleKeyDown, switchToNextMode, switchToPreviousMode])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
acceptInput: () => {
|
||||
|
|
|
|||
|
|
@ -162,8 +162,8 @@ describe("ChatView - Keyboard Shortcut Fix for Dvorak", () => {
|
|||
shiftKey: false,
|
||||
})
|
||||
|
||||
// Wait for event to be processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
// Wait for event to be processed and debounce delay (150ms)
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
|
||||
// Check if mode switch was triggered
|
||||
const callsAfterPeriod = (vscode.postMessage as any).mock.calls
|
||||
|
|
@ -183,7 +183,7 @@ describe("ChatView - Keyboard Shortcut Fix for Dvorak", () => {
|
|||
})
|
||||
|
||||
// Wait for event to be processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
|
||||
// Check that NO mode switch was triggered
|
||||
const callsAfterV = (vscode.postMessage as any).mock.calls
|
||||
|
|
@ -244,8 +244,8 @@ describe("ChatView - Keyboard Shortcut Fix for Dvorak", () => {
|
|||
shiftKey: false,
|
||||
})
|
||||
|
||||
// Wait for event to be processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
// Wait for event to be processed and debounce delay (150ms)
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
|
||||
// Check if mode switch was triggered
|
||||
const calls = (vscode.postMessage as any).mock.calls
|
||||
|
|
@ -277,8 +277,8 @@ describe("ChatView - Keyboard Shortcut Fix for Dvorak", () => {
|
|||
shiftKey: true, // Should go to previous mode
|
||||
})
|
||||
|
||||
// Wait for event to be processed
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
// Wait for event to be processed and debounce delay (150ms)
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
|
||||
// Check if mode switch was triggered
|
||||
const calls = (vscode.postMessage as any).mock.calls
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue