Skip to content

Commit 390fea7

Browse files
committed
disable slash menu if currently navigating through history
1 parent d8d508a commit 390fea7

File tree

7 files changed

+182
-56
lines changed

7 files changed

+182
-56
lines changed

cli/src/chat.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ export const App = ({
9696

9797
const {
9898
inputValue,
99-
setInputValue,
10099
cursorPosition,
100+
lastEditDueToNav,
101+
setInputValue,
101102
setCursorPosition,
102103
inputFocused,
103104
setInputFocused,
@@ -128,8 +129,9 @@ export const App = ({
128129
} = useChatStore(
129130
useShallow((store) => ({
130131
inputValue: store.inputValue,
131-
setInputValue: store.setInputValue,
132132
cursorPosition: store.cursorPosition,
133+
lastEditDueToNav: store.lastEditDueToNav,
134+
setInputValue: store.setInputValue,
133135
setCursorPosition: store.setCursorPosition,
134136
inputFocused: store.inputFocused,
135137
setInputFocused: store.setInputFocused,
@@ -277,7 +279,7 @@ export const App = ({
277279

278280
const handleCtrlC = useCallback(() => {
279281
if (inputValue) {
280-
setInputValue('')
282+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
281283
return true
282284
}
283285

@@ -312,6 +314,37 @@ export const App = ({
312314
localAgents,
313315
})
314316

317+
const [suggestionMenuDisabled, setSuggestionMenuDisabled] = useState(false)
318+
// Disable suggestion menu during navigation
319+
useEffect(() => {
320+
setSuggestionMenuDisabled(!lastEditDueToNav)
321+
}, [setSuggestionMenuDisabled, lastEditDueToNav])
322+
323+
// Disable history navigation during suggesion menu
324+
const [historyNavEnabled, setHistoryNavEnabled] = useState(true)
325+
useEffect(() => {
326+
if (lastEditDueToNav) {
327+
setHistoryNavEnabled(true)
328+
return
329+
}
330+
331+
if (slashContext.active) {
332+
setHistoryNavEnabled(false)
333+
return
334+
}
335+
if (mentionContext.active) {
336+
setHistoryNavEnabled(false)
337+
return
338+
}
339+
340+
setHistoryNavEnabled(true)
341+
}, [
342+
setHistoryNavEnabled,
343+
lastEditDueToNav,
344+
slashContext.active,
345+
mentionContext.active,
346+
])
347+
315348
useEffect(() => {
316349
if (!slashContext.active) {
317350
setSlashSelectedIndex(0)
@@ -545,7 +578,6 @@ export const App = ({
545578
const { saveToHistory, navigateUp, navigateDown } = useInputHistory(
546579
inputValue,
547580
setInputValue,
548-
setCursorPosition,
549581
)
550582

551583
const sendMessageRef = useRef<SendMessageFn>()
@@ -696,6 +728,7 @@ export const App = ({
696728
navigateDown,
697729
toggleAgentMode,
698730
onCtrlC: handleCtrlC,
731+
historyNavEnabled,
699732
})
700733

701734
const { tree: messageTree, topLevelMessages } = useMemo(

cli/src/commands/router.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { runTerminalCommand } from '@codebuff/sdk'
33
import { handleInitializationFlowLocally } from './init'
44

55
import type { MultilineInputHandle } from '../components/multiline-input'
6+
import type { InputValue } from '../state/chat-store'
67
import type { ChatMessage, ContentBlock } from '../types/chat'
78
import type { SendMessageFn } from '../types/contracts/send-message'
89
import type { User } from '../utils/auth'
@@ -26,7 +27,9 @@ export function routeUserPrompt(params: {
2627
sendMessage: SendMessageFn
2728
setCanProcessQueue: (value: React.SetStateAction<boolean>) => void
2829
setInputFocused: (focused: boolean) => void
29-
setInputValue: (value: string | ((prev: string) => string)) => void
30+
setInputValue: (
31+
value: InputValue | ((prev: InputValue) => InputValue),
32+
) => void
3033
setIsAuthenticated: (value: React.SetStateAction<boolean | null>) => void
3134
setMessages: (
3235
value: ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[]),
@@ -120,7 +123,7 @@ export function routeUserPrompt(params: {
120123
})
121124

122125
saveToHistory(trimmed)
123-
setInputValue('')
126+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
124127

125128
return
126129
}
@@ -135,7 +138,7 @@ export function routeUserPrompt(params: {
135138
timestamp: new Date().toISOString(),
136139
}
137140
setMessages((prev) => [...prev, msg])
138-
setInputValue('')
141+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
139142
return
140143
}
141144
if (cmd === 'logout' || cmd === 'signout') {
@@ -152,7 +155,7 @@ export function routeUserPrompt(params: {
152155
timestamp: new Date().toISOString(),
153156
}
154157
setMessages((prev) => [...prev, msg])
155-
setInputValue('')
158+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
156159
setTimeout(() => {
157160
setUser(null)
158161
setIsAuthenticated(false)
@@ -166,7 +169,7 @@ export function routeUserPrompt(params: {
166169
abortControllerRef.current?.abort()
167170
stopStreaming()
168171
setCanProcessQueue(false)
169-
setInputValue('')
172+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
170173
handleCtrlC()
171174
return
172175
}
@@ -176,7 +179,7 @@ export function routeUserPrompt(params: {
176179
clearMessages()
177180

178181
saveToHistory(trimmed)
179-
setInputValue('')
182+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
180183

181184
stopStreaming()
182185
setCanProcessQueue(false)
@@ -189,7 +192,7 @@ export function routeUserPrompt(params: {
189192
}
190193

191194
saveToHistory(trimmed)
192-
setInputValue('')
195+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
193196

194197
if (
195198
isStreaming ||

cli/src/components/multiline-input.tsx

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useOpentuiPaste } from '../hooks/use-opentui-paste'
1414
import { useTheme } from '../hooks/use-theme'
1515
import { computeInputLayoutMetrics } from '../utils/text-layout'
1616

17+
import type { InputValue } from '../state/chat-store'
1718
import type { PasteEvent, ScrollBoxRenderable } from '@opentui/core'
1819

1920
// Helper functions for text manipulation
@@ -80,7 +81,7 @@ function preventKeyDefault(key: KeyWithPreventDefault) {
8081

8182
interface MultilineInputProps {
8283
value: string
83-
onChange: (value: string) => void
84+
onChange: (value: InputValue | ((prev: InputValue) => InputValue)) => void
8485
onSubmit: () => void
8586
onKeyIntercept?: (
8687
key: any,
@@ -153,6 +154,9 @@ export const MultilineInput = forwardRef<
153154
if (cursorPosition > value.length) {
154155
setCursorPosition(value.length)
155156
}
157+
if (cursorPosition < 0) {
158+
setCursorPosition(0)
159+
}
156160
}, [value.length, cursorPosition])
157161

158162
useOpentuiPaste(
@@ -165,8 +169,11 @@ export const MultilineInput = forwardRef<
165169

166170
const newValue =
167171
value.slice(0, cursorPosition) + text + value.slice(cursorPosition)
168-
onChange(newValue)
169-
setCursorPosition(cursorPosition + text.length)
172+
onChange((prev) => ({
173+
text: newValue,
174+
cursorPosition: prev.cursorPosition + text.length,
175+
lastEditDueToNav: false,
176+
}))
170177
},
171178
[focused, value, cursorPosition, onChange],
172179
),
@@ -183,7 +190,6 @@ export const MultilineInput = forwardRef<
183190
scrollBox.verticalScrollBar.scrollPosition = maxScroll
184191
}
185192
}, [value, cursorPosition, focused])
186-
187193
// Measure actual viewport width from the scrollbox to avoid
188194
// wrap miscalculations from heuristic padding/border math.
189195
useEffect(() => {
@@ -206,7 +212,11 @@ export const MultilineInput = forwardRef<
206212
value,
207213
cursorPosition,
208214
setValue: (newValue: string) => {
209-
onChange(newValue)
215+
onChange({
216+
text: newValue,
217+
cursorPosition,
218+
lastEditDueToNav: false,
219+
})
210220
return newValue.length
211221
},
212222
setCursorPosition: (position: number) =>
@@ -270,16 +280,22 @@ export const MultilineInput = forwardRef<
270280
value.slice(0, cursorPosition - 1) +
271281
'\n' +
272282
value.slice(cursorPosition)
273-
onChange(newValue)
274-
setCursorPosition(cursorPosition)
283+
onChange({
284+
text: newValue,
285+
cursorPosition,
286+
lastEditDueToNav: false,
287+
})
275288
return
276289
}
277290

278291
// For other newline shortcuts, just insert newline
279292
const newValue =
280293
value.slice(0, cursorPosition) + '\n' + value.slice(cursorPosition)
281-
onChange(newValue)
282-
setCursorPosition(cursorPosition + 1)
294+
onChange((prev) => ({
295+
text: newValue,
296+
cursorPosition: prev.cursorPosition + 1,
297+
lastEditDueToNav: false,
298+
}))
283299
return
284300
}
285301

@@ -326,8 +342,11 @@ export const MultilineInput = forwardRef<
326342
return
327343
}
328344

329-
onChange(newValue)
330-
setCursorPosition(Math.max(0, nextCursor))
345+
onChange({
346+
text: newValue,
347+
cursorPosition: nextCursor,
348+
lastEditDueToNav: false,
349+
})
331350
return
332351
}
333352

@@ -339,8 +358,11 @@ export const MultilineInput = forwardRef<
339358
preventKeyDefault(key)
340359
const newValue =
341360
value.slice(0, wordStart) + value.slice(cursorPosition)
342-
onChange(newValue)
343-
setCursorPosition(wordStart)
361+
onChange({
362+
text: newValue,
363+
cursorPosition: wordStart,
364+
lastEditDueToNav: false,
365+
})
344366
return
345367
} // Cmd+Delete: Delete to line start; fallback to single delete if nothing changes
346368
if (key.name === 'delete' && key.meta && !isAltLikeModifier) {
@@ -374,22 +396,29 @@ export const MultilineInput = forwardRef<
374396
return
375397
}
376398

377-
onChange(newValue)
378-
setCursorPosition(Math.max(0, nextCursor))
399+
onChange({
400+
text: newValue,
401+
cursorPosition: nextCursor,
402+
lastEditDueToNav: false,
403+
})
379404
return
380405
} // Alt+Delete: Delete word forward
381406
if (key.name === 'delete' && isAltLikeModifier) {
382407
preventKeyDefault(key)
383408
const newValue = value.slice(0, cursorPosition) + value.slice(wordEnd)
384-
onChange(newValue)
409+
onChange({
410+
text: newValue,
411+
cursorPosition,
412+
lastEditDueToNav: false,
413+
})
385414
return
386415
}
387416

388417
// Ctrl+K: Delete to line end
389418
if (key.ctrl && lowerKeyName === 'k' && !key.meta && !key.option) {
390419
preventKeyDefault(key)
391420
const newValue = value.slice(0, cursorPosition) + value.slice(lineEnd)
392-
onChange(newValue)
421+
onChange({ text: newValue, cursorPosition, lastEditDueToNav: true })
393422
return
394423
}
395424

@@ -399,8 +428,11 @@ export const MultilineInput = forwardRef<
399428
if (cursorPosition > 0) {
400429
const newValue =
401430
value.slice(0, cursorPosition - 1) + value.slice(cursorPosition)
402-
onChange(newValue)
403-
setCursorPosition(cursorPosition - 1)
431+
onChange({
432+
text: newValue,
433+
cursorPosition: cursorPosition - 1,
434+
lastEditDueToNav: false,
435+
})
404436
}
405437
return
406438
}
@@ -411,7 +443,11 @@ export const MultilineInput = forwardRef<
411443
if (cursorPosition < value.length) {
412444
const newValue =
413445
value.slice(0, cursorPosition) + value.slice(cursorPosition + 1)
414-
onChange(newValue)
446+
onChange({
447+
text: newValue,
448+
cursorPosition,
449+
lastEditDueToNav: false,
450+
})
415451
}
416452
return
417453
}
@@ -422,8 +458,11 @@ export const MultilineInput = forwardRef<
422458
if (cursorPosition > 0) {
423459
const newValue =
424460
value.slice(0, cursorPosition - 1) + value.slice(cursorPosition)
425-
onChange(newValue)
426-
setCursorPosition(cursorPosition - 1)
461+
onChange({
462+
text: newValue,
463+
cursorPosition: cursorPosition - 1,
464+
lastEditDueToNav: false,
465+
})
427466
}
428467
return
429468
}
@@ -434,7 +473,11 @@ export const MultilineInput = forwardRef<
434473
if (cursorPosition < value.length) {
435474
const newValue =
436475
value.slice(0, cursorPosition) + value.slice(cursorPosition + 1)
437-
onChange(newValue)
476+
onChange({
477+
text: newValue,
478+
cursorPosition,
479+
lastEditDueToNav: false,
480+
})
438481
}
439482
return
440483
}
@@ -544,8 +587,11 @@ export const MultilineInput = forwardRef<
544587
value.slice(0, cursorPosition) +
545588
key.sequence +
546589
value.slice(cursorPosition)
547-
onChange(newValue)
548-
setCursorPosition(cursorPosition + 1)
590+
onChange({
591+
text: newValue,
592+
cursorPosition: cursorPosition + 1,
593+
lastEditDueToNav: false,
594+
})
549595
return
550596
}
551597
},

0 commit comments

Comments
 (0)