44
55use tauri:: { App , WebviewUrl , WebviewWindowBuilder , Wry } ;
66
7+ use crate :: IPC :: WindServiceHandlers :: Utilities :: ReadRecentlyOpened ;
8+
79/// Creates and configures the main application window.
810///
911/// # Arguments
@@ -17,28 +19,67 @@ use tauri::{App, WebviewUrl, WebviewWindowBuilder, Wry};
1719///
1820/// # Platform-Specific Behavior
1921///
20- /// - Windows/macOS/Linux: Sets title, maximized state, no decorations, and
21- /// shadow effect
22- /// - Debug builds: Automatically opens DevTools
22+ /// - **macOS**: `TitleBarStyle::Overlay` + `hidden_title(true)` keeps the
23+ /// traffic-light buttons at the top-left but hides the native title bar
24+ /// strip, so VS Code's custom titlebar (which has `-webkit-app-region:
25+ /// drag` baked into its CSS) lights up the entire top row as a drag
26+ /// region. The previous `maximized(true)` was the direct cause of "can't
27+ /// drag the editor around" - maximized macOS windows are pinned to the
28+ /// screen and refuse all drag events, regardless of `app-region` CSS.
29+ /// - **Windows / Linux**: `decorations(false)` keeps the window chrome-less
30+ /// so the workbench draws its own. We still start `resizable(true)` so
31+ /// the window can be moved by the drag region.
32+ /// - **Debug builds**: DevTools auto-open.
2333pub fn WindowBuild ( Application : & mut App , LocalhostUrl : String ) -> tauri:: WebviewWindow < Wry > {
24- // Create the window URL pointing to the application
34+ // Restore the most-recently-opened folder so the webview boots
35+ // directly into the workspace. Without this, every launch lands
36+ // on the Welcome tab, the user clicks "Open Folder", and the
37+ // `pickFolderAndOpen` handler fires `Window.navigate()` - a hard
38+ // reload that wipes workbench state mid-initialisation and is
39+ // the direct cause of the purple splash flash, stuttering paint,
40+ // empty `@builtin` sidebar, and broken keybindings (every layer
41+ // has to boot twice and the second pass often loses references
42+ // to the first). Using `?folder=...` in the initial URL skips
43+ // that destructive round-trip.
44+ let InitialUrl = BuildInitialUrl ( & LocalhostUrl ) ;
2545 let WindowUrl = WebviewUrl :: External (
26- format ! ( "{}/index.html" , LocalhostUrl )
46+ InitialUrl
2747 . parse ( )
28- . expect ( "FATAL: Failed to parse localhost URL" ) ,
48+ . expect ( "FATAL: Failed to parse initial webview URL" ) ,
2949 ) ;
3050
3151 // Configure window builder with base settings
3252 let mut WindowBuilder = WebviewWindowBuilder :: new ( Application , "main" , WindowUrl )
3353 . use_https_scheme ( false )
3454 . initialization_script ( "" )
3555 . zoom_hotkeys_enabled ( true )
36- . browser_extensions_enabled ( false ) ;
56+ . browser_extensions_enabled ( false )
57+ . title ( "Mountain" )
58+ . resizable ( true )
59+ . inner_size ( 1400.0 , 900.0 )
60+ . shadow ( true ) ;
61+
62+ #[ cfg( target_os = "macos" ) ]
63+ {
64+ // Overlay style lets VS Code's custom titlebar paint behind the
65+ // traffic-light buttons. `hidden_title(true)` suppresses the OS
66+ // title text so it doesn't collide with the workbench menubar.
67+ // `decorations(true)` is REQUIRED for the traffic lights to
68+ // render - turning decorations off also removes the buttons and
69+ // breaks the native drag + resize handles entirely on macOS.
70+ WindowBuilder = WindowBuilder
71+ . title_bar_style ( tauri:: TitleBarStyle :: Overlay )
72+ . hidden_title ( true )
73+ . decorations ( true ) ;
74+ }
3775
38- // Apply platform-specific window configurations
39- #[ cfg( any( target_os = "windows" , target_os = "macos" , target_os = "linux" ) ) ]
76+ #[ cfg( any( target_os = "windows" , target_os = "linux" ) ) ]
4077 {
41- WindowBuilder = WindowBuilder . title ( "Mountain" ) . maximized ( true ) . decorations ( false ) . shadow ( true ) ;
78+ // Keep chrome-less on non-macOS - the workbench provides its
79+ // own window controls via the WindowsTitleBar / LinuxTitleBar
80+ // components. Drag works via `-webkit-app-region: drag` on the
81+ // titlebar element.
82+ WindowBuilder = WindowBuilder . decorations ( false ) ;
4283 }
4384
4485 // Build the main window
@@ -52,3 +93,66 @@ pub fn WindowBuild(Application:&mut App, LocalhostUrl:String) -> tauri::WebviewW
5293
5394 MainWindow
5495}
96+
97+ /// Build the initial webview URL, optionally appending `?folder=<path>`
98+ /// when `~/.land/workspaces/RecentlyOpened.json` has an entry for the
99+ /// previous session's workspace. Falls back to plain `index.html` if
100+ /// the file is missing, malformed, or has no resolvable path.
101+ ///
102+ /// The returned string is already URL-encoded and safe to feed to
103+ /// `WebviewUrl::External`.
104+ fn BuildInitialUrl ( LocalhostUrl : & str ) -> String {
105+ let Base = format ! ( "{}/index.html" , LocalhostUrl ) ;
106+
107+ let Recent = match ReadRecentlyOpened ( ) {
108+ Ok ( Value ) => Value ,
109+ Err ( _) => return Base ,
110+ } ;
111+ let Workspaces = match Recent . get ( "workspaces" ) . and_then ( |V | V . as_array ( ) ) {
112+ Some ( Array ) if !Array . is_empty ( ) => Array ,
113+ _ => return Base ,
114+ } ;
115+
116+ // VS Code's Recently-Opened record can store the folder under a few
117+ // different shapes depending on whether the entry came from the
118+ // extension host, the workbench, or a `$deltaWorkspaceFolders`
119+ // broadcast. Probe them in the same priority order the workbench
120+ // itself uses in `getRecentlyOpenedWorkspaces`.
121+ let Probe = |Entry : & serde_json:: Value | -> Option < String > {
122+ if let Some ( Uri ) = Entry . get ( "folderUri" ) . and_then ( |V | V . as_str ( ) ) {
123+ return Some ( Uri . to_string ( ) ) ;
124+ }
125+ if let Some ( Path ) = Entry . get ( "folderUri" ) . and_then ( |V | V . get ( "path" ) ) . and_then ( |V | V . as_str ( ) ) {
126+ return Some ( Path . to_string ( ) ) ;
127+ }
128+ if let Some ( Path ) =
129+ Entry . get ( "workspace" ) . and_then ( |V | V . get ( "configPath" ) ) . and_then ( |V | V . get ( "path" ) ) . and_then ( |V | V . as_str ( ) )
130+ {
131+ return Some ( Path . to_string ( ) ) ;
132+ }
133+ None
134+ } ;
135+
136+ let FolderPath = match Workspaces . iter ( ) . find_map ( Probe ) {
137+ Some ( Path ) => Path ,
138+ None => return Base ,
139+ } ;
140+
141+ // Strip any `file://` scheme so the query param is a plain path
142+ // the workbench will stringify into a `file:` URI itself; leaving
143+ // the scheme in doubles up and breaks the URL-decode on the other
144+ // side (observed as the second `?folder=` boot path appearing as
145+ // `file:/Volumes/...` in `wb:boot`).
146+ let Normalised = FolderPath
147+ . strip_prefix ( "file://" )
148+ . unwrap_or ( FolderPath . as_str ( ) )
149+ . to_string ( ) ;
150+ if !std:: path:: Path :: new ( & Normalised ) . is_dir ( ) {
151+ return Base ;
152+ }
153+
154+ let Encoded = url:: form_urlencoded:: Serializer :: new ( String :: new ( ) )
155+ . append_pair ( "folder" , & Normalised )
156+ . finish ( ) ;
157+ format ! ( "{}/?{}" , LocalhostUrl , Encoded )
158+ }
0 commit comments