Skip to content

Commit 7e5cd52

Browse files
celestial-vaultElephant Lumps
andauthored
Always allow textarea typing (RooCodeInc#3356)
* enable text area while cline is doing stuff * changeset * add sendingDisabled to dependency array --------- Co-authored-by: Elephant Lumps <[email protected]>
1 parent 4622ad7 commit 7e5cd52

File tree

3 files changed

+45
-41
lines changed

3 files changed

+45
-41
lines changed

.changeset/purple-planes-speak.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": minor
3+
---
4+
5+
Allow the user to form their next message while Cline is taking action

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ interface ChatTextAreaProps {
4444
inputValue: string
4545
activeQuote: string | null
4646
setInputValue: (value: string) => void
47-
textAreaDisabled: boolean
47+
sendingDisabled: boolean
4848
placeholderText: string
4949
selectedImages: string[]
5050
setSelectedImages: React.Dispatch<React.SetStateAction<string[]>>
@@ -229,7 +229,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
229229
inputValue,
230230
activeQuote,
231231
setInputValue,
232-
textAreaDisabled,
232+
sendingDisabled,
233233
placeholderText,
234234
selectedImages,
235235
setSelectedImages,
@@ -519,8 +519,11 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
519519
const isComposing = event.nativeEvent?.isComposing ?? false
520520
if (event.key === "Enter" && !event.shiftKey && !isComposing) {
521521
event.preventDefault()
522-
setIsTextAreaFocused(false)
523-
onSend()
522+
523+
if (!sendingDisabled) {
524+
setIsTextAreaFocused(false)
525+
onSend()
526+
}
524527
}
525528

526529
if (event.key === "Backspace" && !isComposing) {
@@ -604,6 +607,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
604607
selectedSlashCommandsIndex,
605608
slashCommandsQuery,
606609
handleSlashCommandsSelect,
610+
sendingDisabled,
607611
],
608612
)
609613

@@ -916,8 +920,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
916920
useShortcut("Meta+Shift+a", onModeToggle, { disableTextInputs: false }) // important that we don't disable the text input here
917921

918922
const handleContextButtonClick = useCallback(() => {
919-
if (textAreaDisabled) return
920-
921923
// Focus the textarea first
922924
textAreaRef.current?.focus()
923925

@@ -956,7 +958,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
956958
} as React.ChangeEvent<HTMLTextAreaElement>
957959
handleInputChange(event)
958960
updateHighlights()
959-
}, [inputValue, textAreaDisabled, handleInputChange, updateHighlights])
961+
}, [inputValue, handleInputChange, updateHighlights])
960962

961963
// Use an effect to detect menu close
962964
useEffect(() => {
@@ -1248,7 +1250,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
12481250
<div
12491251
style={{
12501252
padding: "10px 15px",
1251-
opacity: textAreaDisabled ? 0.5 : 1,
1253+
opacity: 1,
12521254
position: "relative",
12531255
display: "flex",
12541256
// Drag-over styles moved to DynamicTextArea
@@ -1358,7 +1360,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
13581360
textAreaRef.current = el
13591361
}}
13601362
value={inputValue}
1361-
disabled={textAreaDisabled}
13621363
onChange={(e) => {
13631364
handleInputChange(e)
13641365
updateHighlights()
@@ -1408,7 +1409,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
14081409
// Instead of using boxShadow, we use a div with a border to better replicate the behavior when the textarea is focused
14091410
// boxShadow: "0px 0px 0px 1px var(--vscode-input-border)",
14101411
padding: "9px 28px 3px 9px",
1411-
cursor: textAreaDisabled ? "not-allowed" : undefined,
1412+
cursor: "text",
14121413
flex: 1,
14131414
zIndex: 1,
14141415
outline:
@@ -1466,9 +1467,9 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
14661467
/> */}
14671468
<div
14681469
data-testid="send-button"
1469-
className={`input-icon-button ${textAreaDisabled ? "disabled" : ""} codicon codicon-send`}
1470+
className={`input-icon-button ${sendingDisabled ? "disabled" : ""} codicon codicon-send`}
14701471
onClick={() => {
1471-
if (!textAreaDisabled) {
1472+
if (!sendingDisabled) {
14721473
setIsTextAreaFocused(false)
14731474
onSend()
14741475
}
@@ -1504,7 +1505,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
15041505
data-testid="context-button"
15051506
appearance="icon"
15061507
aria-label="Add Context"
1507-
disabled={textAreaDisabled}
15081508
onClick={handleContextButtonClick}
15091509
style={{ padding: "0px 0px", height: "20px" }}>
15101510
<ButtonContainer>

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
9090
const [activeQuote, setActiveQuote] = useState<string | null>(null)
9191
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
9292
const textAreaRef = useRef<HTMLTextAreaElement>(null)
93-
const [textAreaDisabled, setTextAreaDisabled] = useState(false)
93+
const [sendingDisabled, setSendingDisabled] = useState(false)
9494
const [selectedImages, setSelectedImages] = useState<string[]>([])
9595

9696
// we need to hold on to the ask because useEffect > lastMessage will always let us know when an ask comes in and handle it, but by the time handleMessage is called, the last message might not be the ask anymore (it could be a say that followed)
@@ -147,42 +147,42 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
147147
const isPartial = lastMessage.partial === true
148148
switch (lastMessage.ask) {
149149
case "api_req_failed":
150-
setTextAreaDisabled(true)
150+
setSendingDisabled(true)
151151
setClineAsk("api_req_failed")
152152
setEnableButtons(true)
153153
setPrimaryButtonText("Retry")
154154
setSecondaryButtonText("Start New Task")
155155
break
156156
case "mistake_limit_reached":
157-
setTextAreaDisabled(false)
157+
setSendingDisabled(false)
158158
setClineAsk("mistake_limit_reached")
159159
setEnableButtons(true)
160160
setPrimaryButtonText("Proceed Anyways")
161161
setSecondaryButtonText("Start New Task")
162162
break
163163
case "auto_approval_max_req_reached":
164-
setTextAreaDisabled(true)
164+
setSendingDisabled(true)
165165
setClineAsk("auto_approval_max_req_reached")
166166
setEnableButtons(true)
167167
setPrimaryButtonText("Proceed")
168168
setSecondaryButtonText("Start New Task")
169169
break
170170
case "followup":
171-
setTextAreaDisabled(isPartial)
171+
setSendingDisabled(isPartial)
172172
setClineAsk("followup")
173173
setEnableButtons(false)
174174
// setPrimaryButtonText(undefined)
175175
// setSecondaryButtonText(undefined)
176176
break
177177
case "plan_mode_respond":
178-
setTextAreaDisabled(isPartial)
178+
setSendingDisabled(isPartial)
179179
setClineAsk("plan_mode_respond")
180180
setEnableButtons(false)
181181
// setPrimaryButtonText(undefined)
182182
// setSecondaryButtonText(undefined)
183183
break
184184
case "tool":
185-
setTextAreaDisabled(isPartial)
185+
setSendingDisabled(isPartial)
186186
setClineAsk("tool")
187187
setEnableButtons(!isPartial)
188188
const tool = JSON.parse(lastMessage.text || "{}") as ClineSayTool
@@ -199,66 +199,66 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
199199
}
200200
break
201201
case "browser_action_launch":
202-
setTextAreaDisabled(isPartial)
202+
setSendingDisabled(isPartial)
203203
setClineAsk("browser_action_launch")
204204
setEnableButtons(!isPartial)
205205
setPrimaryButtonText("Approve")
206206
setSecondaryButtonText("Reject")
207207
break
208208
case "command":
209-
setTextAreaDisabled(isPartial)
209+
setSendingDisabled(isPartial)
210210
setClineAsk("command")
211211
setEnableButtons(!isPartial)
212212
setPrimaryButtonText("Run Command")
213213
setSecondaryButtonText("Reject")
214214
break
215215
case "command_output":
216-
setTextAreaDisabled(false)
216+
setSendingDisabled(false)
217217
setClineAsk("command_output")
218218
setEnableButtons(true)
219219
setPrimaryButtonText("Proceed While Running")
220220
setSecondaryButtonText(undefined)
221221
break
222222
case "use_mcp_server":
223-
setTextAreaDisabled(isPartial)
223+
setSendingDisabled(isPartial)
224224
setClineAsk("use_mcp_server")
225225
setEnableButtons(!isPartial)
226226
setPrimaryButtonText("Approve")
227227
setSecondaryButtonText("Reject")
228228
break
229229
case "completion_result":
230230
// extension waiting for feedback. but we can just present a new task button
231-
setTextAreaDisabled(isPartial)
231+
setSendingDisabled(isPartial)
232232
setClineAsk("completion_result")
233233
setEnableButtons(!isPartial)
234234
setPrimaryButtonText("Start New Task")
235235
setSecondaryButtonText(undefined)
236236
break
237237
case "resume_task":
238-
setTextAreaDisabled(false)
238+
setSendingDisabled(false)
239239
setClineAsk("resume_task")
240240
setEnableButtons(true)
241241
setPrimaryButtonText("Resume Task")
242242
setSecondaryButtonText(undefined)
243243
setDidClickCancel(false) // special case where we reset the cancel button state
244244
break
245245
case "resume_completed_task":
246-
setTextAreaDisabled(false)
246+
setSendingDisabled(false)
247247
setClineAsk("resume_completed_task")
248248
setEnableButtons(true)
249249
setPrimaryButtonText("Start New Task")
250250
setSecondaryButtonText(undefined)
251251
setDidClickCancel(false)
252252
break
253253
case "new_task":
254-
setTextAreaDisabled(isPartial)
254+
setSendingDisabled(isPartial)
255255
setClineAsk("new_task")
256256
setEnableButtons(!isPartial)
257257
setPrimaryButtonText("Start New Task with Context")
258258
setSecondaryButtonText(undefined)
259259
break
260260
case "condense":
261-
setTextAreaDisabled(isPartial)
261+
setSendingDisabled(isPartial)
262262
setClineAsk("condense")
263263
setEnableButtons(!isPartial)
264264
setPrimaryButtonText("Condense Conversation")
@@ -273,7 +273,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
273273
if (secondLastMessage?.ask === "command_output") {
274274
// if the last ask is a command_output, and we receive an api_req_started, then that means the command has finished and we don't need input from the user anymore (in every other case, the user has to interact with input field or buttons to continue, which does the following automatically)
275275
setInputValue("")
276-
setTextAreaDisabled(true)
276+
setSendingDisabled(true)
277277
setSelectedImages([])
278278
setClineAsk(undefined)
279279
setEnableButtons(false)
@@ -310,7 +310,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
310310

311311
useEffect(() => {
312312
if (messages.length === 0) {
313-
setTextAreaDisabled(false)
313+
setSendingDisabled(false)
314314
setClineAsk(undefined)
315315
setEnableButtons(false)
316316
setPrimaryButtonText("Approve")
@@ -397,7 +397,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
397397
}
398398
setInputValue("")
399399
setActiveQuote(null) // Clear quote when sending message
400-
setTextAreaDisabled(true)
400+
setSendingDisabled(true)
401401
setSelectedImages([])
402402
setClineAsk(undefined)
403403
setEnableButtons(false)
@@ -467,7 +467,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
467467
})
468468
break
469469
}
470-
setTextAreaDisabled(true)
470+
setSendingDisabled(true)
471471
setClineAsk(undefined)
472472
setEnableButtons(false)
473473
// setPrimaryButtonText(undefined)
@@ -516,7 +516,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
516516
setSelectedImages([])
517517
break
518518
}
519-
setTextAreaDisabled(true)
519+
setSendingDisabled(true)
520520
setClineAsk(undefined)
521521
setEnableButtons(false)
522522
// setPrimaryButtonText(undefined)
@@ -542,8 +542,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
542542
vscode.postMessage({ type: "selectImages" })
543543
}, [])
544544

545-
const shouldDisableImages =
546-
!selectedModelInfo.supportsImages || textAreaDisabled || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
545+
const shouldDisableImages = !selectedModelInfo.supportsImages || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
547546

548547
const handleMessage = useCallback(
549548
(e: MessageEvent) => {
@@ -552,7 +551,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
552551
case "action":
553552
switch (message.action!) {
554553
case "didBecomeVisible":
555-
if (!isHidden && !textAreaDisabled && !enableButtons) {
554+
if (!isHidden && !sendingDisabled && !enableButtons) {
556555
textAreaRef.current?.focus()
557556
}
558557
break
@@ -601,7 +600,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
601600
}
602601
// textAreaRef.current is not explicitly required here since react guarantees that ref will be stable across re-renders, and we're not using its value but its reference.
603602
},
604-
[isHidden, textAreaDisabled, enableButtons, handleSendMessage, handlePrimaryButtonClick, handleSecondaryButtonClick],
603+
[isHidden, sendingDisabled, enableButtons, handleSendMessage, handlePrimaryButtonClick, handleSecondaryButtonClick],
605604
)
606605

607606
useEvent("message", handleMessage)
@@ -613,14 +612,14 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
613612

614613
useEffect(() => {
615614
const timer = setTimeout(() => {
616-
if (!isHidden && !textAreaDisabled && !enableButtons) {
615+
if (!isHidden && !sendingDisabled && !enableButtons) {
617616
textAreaRef.current?.focus()
618617
}
619618
}, 50)
620619
return () => {
621620
clearTimeout(timer)
622621
}
623-
}, [isHidden, textAreaDisabled, enableButtons])
622+
}, [isHidden, sendingDisabled, enableButtons])
624623

625624
const visibleMessages = useMemo(() => {
626625
return modifiedMessages.filter((message) => {
@@ -1096,7 +1095,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
10961095
activeQuote={activeQuote}
10971096
inputValue={inputValue}
10981097
setInputValue={setInputValue}
1099-
textAreaDisabled={textAreaDisabled}
1098+
sendingDisabled={sendingDisabled}
11001099
placeholderText={placeholderText}
11011100
selectedImages={selectedImages}
11021101
setSelectedImages={setSelectedImages}

0 commit comments

Comments
 (0)