33
44import { CenteredDiv } from "@/app/element/quickelems" ;
55import { globalStore } from "@/app/store/jotaiStore" ;
6+ import { RpcApi } from "@/app/store/wshclientapi" ;
67import { TabRpcClient } from "@/app/store/wshrpcutil" ;
78import { BlockHeaderSuggestionControl } from "@/app/suggestion/suggestion" ;
89import { useWaveEnv } from "@/app/waveenv/waveenv" ;
@@ -21,17 +22,48 @@ import type { PreviewModel } from "./preview-model";
2122import { StreamingPreview } from "./preview-streaming" ;
2223import type { PreviewEnv } from "./previewenv" ;
2324
24- function shellEscapePath ( path : string ) : string {
25+ function posixEscapePath ( path : string ) : string {
2526 if ( path === "~" ) return "~" ;
2627 if ( path . startsWith ( "~/" ) ) {
2728 return "~/" + "'" + path . slice ( 2 ) . replace ( / ' / g, "'\\''" ) + "'" ;
2829 }
2930 return "'" + path . replace ( / ' / g, "'\\''" ) + "'" ;
3031}
3132
32- async function sendCdToTerminal ( termBlockId : string , path : string , env : import ( "./previewenv" ) . PreviewEnv ) {
33- const command = "\x15cd " + shellEscapePath ( path ) + "\r" ;
34- await env . rpc . ControllerInputCommand ( TabRpcClient , { blockid : termBlockId , inputdata64 : stringToBase64 ( command ) } ) ;
33+ function pwshEscapePath ( path : string ) : string {
34+ return "'" + path . replace ( / ' / g, "''" ) + "'" ;
35+ }
36+
37+ function cmdEscapePath ( path : string ) : string {
38+ return '"' + path . replace ( / " / g, '""' ) + '"' ;
39+ }
40+
41+ function buildCdCommand ( shellType : string , path : string ) : string {
42+ if ( shellType === "pwsh" || shellType === "powershell" ) {
43+ return "\x1bSet-Location -LiteralPath " + pwshEscapePath ( path ) + "\r" ;
44+ }
45+ if ( shellType === "cmd" ) {
46+ return "\x1bcd /d " + cmdEscapePath ( path ) + "\r" ;
47+ }
48+ return "\x15cd " + posixEscapePath ( path ) + "\r" ;
49+ }
50+
51+ async function sendCdToTerminal ( termBlockId : string , path : string ) {
52+ const block = WOS . getObjectValue < Block > ( WOS . makeORef ( "block" , termBlockId ) , globalStore . get ) ;
53+ if ( block ?. meta ?. view !== "term" ) {
54+ return ;
55+ }
56+ let shellType = "" ;
57+ try {
58+ const rtInfo = await RpcApi . GetRTInfoCommand ( TabRpcClient , {
59+ oref : WOS . makeORef ( "block" , termBlockId ) ,
60+ } ) ;
61+ shellType = rtInfo ?. [ "shell:type" ] ?? "" ;
62+ } catch {
63+ // fall through with empty shellType, defaults to POSIX
64+ }
65+ const command = buildCdCommand ( shellType , path ) ;
66+ await RpcApi . ControllerInputCommand ( TabRpcClient , { blockid : termBlockId , inputdata64 : stringToBase64 ( command ) } ) ;
3567}
3668
3769export type SpecializedViewProps = {
@@ -117,17 +149,24 @@ function FollowTermDropdown({ model }: { model: PreviewModel }) {
117149 const menuRef = useRef < HTMLDivElement > ( null ) ;
118150 const previousActiveElement = useRef < Element | null > ( null ) ;
119151
120- const closeMenu = React . useCallback ( ( ) => {
121- BlockModel . getInstance ( ) . setBlockHighlight ( null ) ;
122- globalStore . set ( model . followTermMenuDataAtom , null ) ;
152+ const restoreFocus = React . useCallback ( ( ) => {
123153 if ( previousActiveElement . current instanceof HTMLElement ) {
124154 previousActiveElement . current . focus ( ) ;
125155 }
126- } , [ model . followTermMenuDataAtom ] ) ;
156+ previousActiveElement . current = null ;
157+ } , [ ] ) ;
158+
159+ const closeMenu = React . useCallback ( ( ) => {
160+ BlockModel . getInstance ( ) . setBlockHighlight ( null ) ;
161+ globalStore . set ( model . followTermMenuDataAtom , null ) ;
162+ restoreFocus ( ) ;
163+ } , [ model . followTermMenuDataAtom , restoreFocus ] ) ;
127164
128165 useEffect ( ( ) => {
129166 if ( ! menuData ) return ;
130- previousActiveElement . current = document . activeElement ;
167+ if ( previousActiveElement . current === null ) {
168+ previousActiveElement . current = document . activeElement ;
169+ }
131170 const handleEscape = ( e : KeyboardEvent ) => {
132171 if ( e . key === "Escape" ) {
133172 closeMenu ( ) ;
@@ -158,9 +197,7 @@ function FollowTermDropdown({ model }: { model: PreviewModel }) {
158197 await model . env . services . object . UpdateObjectMeta ( WOS . makeORef ( "block" , model . blockId ) , updates ) ;
159198 } ) ;
160199 globalStore . set ( model . followTermMenuDataAtom , null ) ;
161- if ( previousActiveElement . current instanceof HTMLElement ) {
162- previousActiveElement . current . focus ( ) ;
163- }
200+ restoreFocus ( ) ;
164201 } ;
165202 const toggleBidir = ( ) => {
166203 fireAndForget ( async ( ) => {
@@ -178,9 +215,7 @@ function FollowTermDropdown({ model }: { model: PreviewModel }) {
178215 } ) ;
179216 } ) ;
180217 globalStore . set ( model . followTermMenuDataAtom , null ) ;
181- if ( previousActiveElement . current instanceof HTMLElement ) {
182- previousActiveElement . current . focus ( ) ;
183- }
218+ restoreFocus ( ) ;
184219 } ;
185220
186221 const dropdownStyle : React . CSSProperties = {
@@ -308,10 +343,19 @@ function PreviewView({
308343
309344 useEffect ( ( ) => {
310345 if ( ! followTermId || ! followTermCwd ) return ;
311- suppressBidirRef . current = true ;
346+ const currentPath = globalStore . get ( model . metaFilePath ) ?? "" ;
347+ if ( followTermCwd !== currentPath ) {
348+ suppressBidirRef . current = true ;
349+ }
312350 fireAndForget ( ( ) => model . goHistory ( followTermCwd ) ) ;
313351 } , [ followTermCwd , followTermId , model ] ) ;
314352
353+ useEffect ( ( ) => {
354+ if ( ! followTermBidir ) {
355+ suppressBidirRef . current = false ;
356+ }
357+ } , [ followTermBidir ] ) ;
358+
315359 useEffect ( ( ) => {
316360 if ( ! followTermId || ! followTermBidir ) return ;
317361 if ( suppressBidirRef . current ) {
@@ -321,8 +365,8 @@ function PreviewView({
321365 if ( loadableFileInfo . state !== "hasData" ) return ;
322366 const fi = loadableFileInfo . data ;
323367 if ( ! fi || fi . mimetype !== "directory" || ! fi . path ) return ;
324- fireAndForget ( ( ) => sendCdToTerminal ( followTermId , fi . path , env ) ) ;
325- } , [ loadableFileInfo , followTermId , followTermBidir , env ] ) ;
368+ fireAndForget ( ( ) => sendCdToTerminal ( followTermId , fi . path ) ) ;
369+ } , [ loadableFileInfo , followTermId , followTermBidir ] ) ;
326370
327371 if ( connStatus ?. status != "connected" ) {
328372 return null ;
0 commit comments