@@ -10,6 +10,27 @@ import type { Node } from "unist"
1010import { useExtensionState } from "@/context/ExtensionStateContext"
1111import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
1212import MermaidBlock from "@/components/common/MermaidBlock"
13+ import { vscode } from "@/utils/vscode"
14+
15+ // Styled component for Act Mode text with more specific styling
16+ const ActModeHighlight : React . FC = ( ) => (
17+ < span
18+ onClick = { ( ) => {
19+ vscode . postMessage ( {
20+ type : "togglePlanActMode" ,
21+ chatSettings : {
22+ mode : "act" ,
23+ } ,
24+ } )
25+ } }
26+ title = "Click to toggle to Act Mode"
27+ className = "text-[var(--vscode-textLink-foreground)] hover:opacity-90 cursor-pointer inline-flex items-center gap-1" >
28+ < div className = "p-1 rounded-[12px] bg-[var(--vscode-editor-background)] flex items-center justify-end w-4 border-[1px] border-[var(--vscode-input-border)]" >
29+ < div className = "rounded-full bg-[var(--vscode-textLink-foreground)] w-2 h-2" />
30+ </ div >
31+ Act Mode (⌘⇧A)
32+ </ span >
33+ )
1334
1435interface MarkdownBlockProps {
1536 markdown ?: string
@@ -55,6 +76,68 @@ const remarkUrlToLink = () => {
5576 }
5677}
5778
79+ /**
80+ * Custom remark plugin that highlights "to Act Mode" mentions and adds keyboard shortcut hint
81+ */
82+ const remarkHighlightActMode = ( ) => {
83+ return ( tree : Node ) => {
84+ visit ( tree , "text" , ( node : any , index , parent ) => {
85+ // Case-insensitive regex to match "to Act Mode" in various capitalizations
86+ // Using word boundaries to avoid matching within words
87+ // Added negative lookahead to avoid matching if already followed by the shortcut
88+ const actModeRegex = / \b t o \s + A c t \s + M o d e \b (? ! \s * \( ⌘ ⇧ A \) ) / i
89+
90+ if ( ! node . value . match ( actModeRegex ) ) return
91+
92+ // Split the text by the matches
93+ const parts = node . value . split ( actModeRegex )
94+ const matches = node . value . match ( actModeRegex )
95+
96+ if ( ! matches || parts . length <= 1 ) return
97+
98+ const children : any [ ] = [ ]
99+
100+ parts . forEach ( ( part : string , i : number ) => {
101+ // Add the text before the match
102+ if ( part ) children . push ( { type : "text" , value : part } )
103+
104+ // Add the match, but only make "Act Mode" bold (not the "to" part)
105+ if ( matches [ i ] ) {
106+ // Extract "to" and "Act Mode" parts
107+ const matchText = matches [ i ]
108+ const toIndex = matchText . toLowerCase ( ) . indexOf ( "to" )
109+ const actModeIndex = matchText . toLowerCase ( ) . indexOf ( "act mode" , toIndex + 2 )
110+
111+ if ( toIndex !== - 1 && actModeIndex !== - 1 ) {
112+ // Add "to" as regular text
113+ const toPart = matchText . substring ( toIndex , actModeIndex ) . trim ( )
114+ children . push ( { type : "text" , value : toPart + " " } )
115+
116+ // Add "Act Mode" as bold with keyboard shortcut
117+ const actModePart = matchText . substring ( actModeIndex )
118+ children . push ( {
119+ type : "strong" ,
120+ children : [ { type : "text" , value : `${ actModePart } (⌘⇧A)` } ] ,
121+ } )
122+ } else {
123+ // Fallback if we can't parse it correctly
124+ children . push ( { type : "text" , value : matchText + " " } )
125+ children . push ( {
126+ type : "strong" ,
127+ children : [ { type : "text" , value : `(⌘⇧A)` } ] ,
128+ } )
129+ }
130+ }
131+ } )
132+
133+ // Replace the original text node with our new nodes
134+ if ( parent ) {
135+ parent . children . splice ( index , 1 , ...children )
136+ }
137+ } )
138+ }
139+ }
140+
58141/**
59142 * Custom remark plugin that prevents filenames with extensions from being parsed as bold text
60143 * For example: __init__.py should not be rendered as bold "init" followed by ".py"
@@ -286,6 +369,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
286369 remarkPlugins : [
287370 remarkPreventBoldFilenames ,
288371 remarkUrlToLink ,
372+ remarkHighlightActMode ,
289373 remarkMath ,
290374 ( ) => {
291375 return ( tree ) => {
@@ -329,6 +413,27 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
329413 }
330414 return < code { ...props } />
331415 } ,
416+ strong : ( props : ComponentProps < "strong" > ) => {
417+ // Check if this is an "Act Mode" strong element by looking for the keyboard shortcut
418+ // Handle both string children and array of children cases
419+ const childrenText = React . Children . toArray ( props . children )
420+ . map ( ( child ) => {
421+ if ( typeof child === "string" ) return child
422+ if ( typeof child === "object" && "props" in child && child . props . children )
423+ return String ( child . props . children )
424+ return ""
425+ } )
426+ . join ( "" )
427+
428+ // Case-insensitive check for "Act Mode (⌘⇧A)" pattern
429+ // This ensures we only style the exact "Act Mode" mentions with keyboard shortcut
430+ // Using case-insensitive flag to catch all capitalization variations
431+ if ( / ^ a c t m o d e \s * \( ⌘ ⇧ A \) $ / i. test ( childrenText ) ) {
432+ return < ActModeHighlight />
433+ }
434+
435+ return < strong { ...props } />
436+ } ,
332437 } ,
333438 } ,
334439 } )
0 commit comments