@@ -15,6 +15,9 @@ import "@codingame/monaco-vscode-swift-default-extension";
1515import "@codingame/monaco-vscode-theme-defaults-default-extension" ;
1616import { platform } from "@tauri-apps/plugin-os" ;
1717import { TabLike } from "../TabLike" ;
18+ import { useStore } from "../../utilities/StoreContext" ;
19+ import { useParams } from "react-router" ;
20+ import { readTextFile , writeTextFile } from "@tauri-apps/plugin-fs" ;
1821
1922export type WorkerLoader = ( ) => Worker ;
2023const workerLoaders : Partial < Record < string , WorkerLoader > > = {
@@ -47,7 +50,7 @@ export interface EditorProps {
4750 openFiles : string [ ] ;
4851 setOpenFiles : Dispatch < SetStateAction < string [ ] > > ;
4952 focusedFile : string | null ;
50- setSaveFile : Dispatch < SetStateAction < ( ( ) => void ) | null > > ;
53+ setSaveFile : Dispatch < SetStateAction < ( ( ) => Promise < void > ) | null > > ;
5154 setUndo : Dispatch < SetStateAction < ( ( ) => void ) | null > > ;
5255 setRedo : Dispatch < SetStateAction < ( ( ) => void ) | null > > ;
5356 openNewFile : ( file : string ) => void ;
@@ -101,8 +104,69 @@ export default ({
101104 const openNewFileRef = useRef ( openNewFile ) ;
102105 const selectionOverrideRef = useRef < ITextEditorOptions | null > ( null ) ;
103106 const hasInitializedRef = useRef ( false ) ;
107+ const scrollStates = useRef < {
108+ [ key : string ] : [ number , number ] ;
109+ } > ( { } ) ;
110+ const [ hoveredOnBtn , setHoveredOnBtn ] = useState < number | null > ( null ) ;
111+ const [ formatOnSave ] = useStore < boolean > ( "sourcekit/format" , true ) ;
104112
105- let [ hoveredOnBtn , setHoveredOnBtn ] = useState < number | null > ( null ) ;
113+ const { path : filePath } = useParams < "path" > ( ) ;
114+ const hasAttemptedToReadOpenFiles = useRef < string | null > ( null ) ;
115+
116+ useEffect ( ( ) => {
117+ ( async ( ) => {
118+ if ( ! filePath || hasAttemptedToReadOpenFiles . current === filePath ) return ;
119+ const savePath = await path . join (
120+ filePath ,
121+ ".crosscode" ,
122+ "openFiles.json"
123+ ) ;
124+ try {
125+ let text = await readTextFile ( savePath ) ;
126+ if ( ! text ) return ;
127+ let data = JSON . parse ( text ) as { files : string [ ] ; focused : number } ;
128+ // make sure that types are as expected
129+ if (
130+ ! data ||
131+ typeof data !== "object" ||
132+ ! Array . isArray ( data . files ) ||
133+ typeof data . focused !== "number"
134+ )
135+ return ;
136+ data . files = data . files . filter ( ( file ) => typeof file === "string" ) ;
137+ if ( data . files . length === 0 ) return ;
138+ if ( data . focused < 0 || data . focused >= data . files . length ) {
139+ data . focused = 0 ;
140+ }
141+ setOpenFiles ( data . files ) ;
142+ setTimeout ( ( ) => {
143+ setFocused ( data . focused ) ;
144+ } , 100 ) ;
145+ } catch ( e ) {
146+ void e ;
147+ } finally {
148+ hasAttemptedToReadOpenFiles . current = filePath ;
149+ }
150+ } ) ( ) ;
151+ } , [ filePath , setOpenFiles , setFocused ] ) ;
152+
153+ useEffect ( ( ) => {
154+ ( async ( ) => {
155+ if ( ! filePath || hasAttemptedToReadOpenFiles . current !== filePath ) return ;
156+ const savePath = await path . join (
157+ filePath ,
158+ ".crosscode" ,
159+ "openFiles.json"
160+ ) ;
161+ let data = {
162+ files : openFiles ,
163+ focused : focused ?? 0 ,
164+ } ;
165+ writeTextFile ( savePath , JSON . stringify ( data ) ) . catch ( ( err ) => {
166+ console . error ( "Error writing openFiles.json:" , err ) ;
167+ } ) ;
168+ } ) ( ) ;
169+ } , [ openFiles , filePath , focused ] ) ;
106170
107171 useEffect ( ( ) => {
108172 globalEditorServiceCallbacks . currentTabsRef = currentTabsRef ;
@@ -229,6 +293,18 @@ export default ({
229293 } ;
230294 } , [ initialized ] ) ;
231295
296+ useEffect ( ( ) => {
297+ if ( editor === null ) return ;
298+ let listener = editor . onDidScrollChange ( ( e ) => {
299+ if ( focused !== undefined ) {
300+ scrollStates . current [ tabs [ focused ] . file ] = [ e . scrollTop , e . scrollLeft ] ;
301+ }
302+ } ) ;
303+ return ( ) => {
304+ listener . dispose ( ) ;
305+ } ;
306+ } , [ editor , focused , tabs ] ) ;
307+
232308 useEffect ( ( ) => {
233309 if ( ! editor || ! initialized ) return ;
234310 monaco . editor . setTheme ( mode === "dark" ? "vs-dark" : "vs" ) ;
@@ -376,10 +452,22 @@ export default ({
376452 }
377453 } ) ;
378454 } ) ;
379- setSaveFile ( ( ) => modelRef . object . save . bind ( modelRef . object ) ) ;
455+
456+ setSaveFile ( ( ) => async ( ) => {
457+ if ( formatOnSave ) {
458+ await editor . getAction ( "editor.action.formatDocument" ) ?. run ( ) ;
459+ }
460+ await modelRef . object . save ( ) ;
461+ } ) ;
380462
381463 editor . setModel ( modelRef . object . textEditorModel ) ;
382464
465+ if ( scrollStates . current && scrollStates . current [ filePath ] ) {
466+ const [ scrollTop , scrollLeft ] = scrollStates . current [ filePath ] ;
467+ editor . setScrollTop ( scrollTop ) ;
468+ editor . setScrollLeft ( scrollLeft ) ;
469+ }
470+
383471 // I don't love doing it like this but it seems to improve consistency over just running it directly
384472 requestAnimationFrame ( ( ) => {
385473 if (
@@ -409,7 +497,7 @@ export default ({
409497 } ) ;
410498 } ;
411499 switchFile ( ) ;
412- } , [ focused , editor , tabs ] ) ;
500+ } , [ focused , editor , tabs , formatOnSave ] ) ;
413501
414502 return (
415503 < div className = { "editor" } >
@@ -418,6 +506,12 @@ export default ({
418506 onDragOver = { handleContainerDragOver }
419507 onDragLeave = { handleDragLeave }
420508 onDrop = { handleDrop }
509+ onWheel = { ( e ) => {
510+ if ( e . deltaY !== 0 ) {
511+ e . currentTarget . scrollLeft += e . deltaY ;
512+ e . preventDefault ( ) ;
513+ }
514+ } }
421515 >
422516 { tabs . map ( ( tab , index ) => {
423517 let unsaved = unsavedFiles . includes ( tab . file ) ;
0 commit comments