Bläddra i källkod

Add fileOps API, folder tree UI & context menus

Toby Chui 3 dagar sedan
förälder
incheckning
d46e244c87

+ 230 - 0
src/web/Code Studio/CodeStudio.css

@@ -93,6 +93,14 @@
     color:rgba(255, 255, 255, 0.747);
 }  
 
+#directoryList .subgroup {
+    min-height: 50px;
+}
+
+#directoryExplorer {
+    min-height: 100px;
+}
+
 #directoryList .subgroup .item{
     color:white;
     padding:2px;
@@ -278,3 +286,225 @@
     border: 0px solid transparent;
 }
 
+/* Folder Tree Styles */
+#directoryList .folderObjectWrapper {
+    position: relative;
+}
+
+#directoryList .folderObjectWrapper > .item,
+#directoryList .folderObjectWrapper > .subitem {
+    padding-left: 20px;
+}
+
+#directoryList .dirlist {
+    border-left: 1px solid #444;
+    margin-left: 16px;
+}
+
+/* Colored folder icons based on folder type */
+.folder-icon {
+    margin-right: 4px;
+}
+
+.folder-icon.src { color: #4ec9b0 !important; }
+.folder-icon.lib { color: #dcdcaa !important; }
+.folder-icon.test { color: #ce9178 !important; }
+.folder-icon.tests { color: #ce9178 !important; }
+.folder-icon.spec { color: #ce9178 !important; }
+.folder-icon.config { color: #9cdcfe !important; }
+.folder-icon.public { color: #4fc1ff !important; }
+.folder-icon.static { color: #4fc1ff !important; }
+.folder-icon.assets { color: #c586c0 !important; }
+.folder-icon.images { color: #c586c0 !important; }
+.folder-icon.img { color: #c586c0 !important; }
+.folder-icon.css { color: #569cd6 !important; }
+.folder-icon.styles { color: #569cd6 !important; }
+.folder-icon.js { color: #dcdcaa !important; }
+.folder-icon.scripts { color: #dcdcaa !important; }
+.folder-icon.components { color: #4ec9b0 !important; }
+.folder-icon.views { color: #6a9955 !important; }
+.folder-icon.pages { color: #6a9955 !important; }
+.folder-icon.templates { color: #6a9955 !important; }
+.folder-icon.models { color: #d7ba7d !important; }
+.folder-icon.controllers { color: #d7ba7d !important; }
+.folder-icon.api { color: #4fc1ff !important; }
+.folder-icon.routes { color: #4fc1ff !important; }
+.folder-icon.middleware { color: #ce9178 !important; }
+.folder-icon.utils { color: #dcdcaa !important; }
+.folder-icon.helpers { color: #dcdcaa !important; }
+.folder-icon.vendor { color: #808080 !important; }
+.folder-icon.node_modules { color: #6d8086 !important; }
+.folder-icon.build { color: #d4d4d4 !important; }
+.folder-icon.dist { color: #d4d4d4 !important; }
+.folder-icon.out { color: #d4d4d4 !important; }
+.folder-icon.bin { color: #d4d4d4 !important; }
+.folder-icon.docs { color: #569cd6 !important; }
+.folder-icon.documentation { color: #569cd6 !important; }
+.folder-icon.backend { color: #4ec9b0 !important; }
+.folder-icon.frontend { color: #ce9178 !important; }
+.folder-icon.server { color: #4ec9b0 !important; }
+.folder-icon.client { color: #ce9178 !important; }
+.folder-icon.database { color: #d7ba7d !important; }
+.folder-icon.db { color: #d7ba7d !important; }
+.folder-icon.migrations { color: #d7ba7d !important; }
+.folder-icon.seeds { color: #d7ba7d !important; }
+
+/* Default folder color */
+.folder.icon {
+    color: #dcb67a !important;
+}
+
+/* File icon colors based on extension */
+.file-icon-js { color: #f1e05a !important; }
+.file-icon-ts { color: #3178c6 !important; }
+.file-icon-jsx { color: #61dafb !important; }
+.file-icon-tsx { color: #3178c6 !important; }
+.file-icon-html { color: #e34c26 !important; }
+.file-icon-htm { color: #e34c26 !important; }
+.file-icon-css { color: #563d7c !important; }
+.file-icon-scss { color: #c6538c !important; }
+.file-icon-sass { color: #c6538c !important; }
+.file-icon-less { color: #1d365d !important; }
+.file-icon-json { color: #f1e05a !important; }
+.file-icon-xml { color: #e34c26 !important; }
+.file-icon-md { color: #083fa1 !important; }
+.file-icon-py { color: #3572a5 !important; }
+.file-icon-go { color: #00add8 !important; }
+.file-icon-java { color: #b07219 !important; }
+.file-icon-c { color: #555555 !important; }
+.file-icon-cpp { color: #f34b7d !important; }
+.file-icon-cs { color: #178600 !important; }
+.file-icon-php { color: #4f5d95 !important; }
+.file-icon-rb { color: #701516 !important; }
+.file-icon-rs { color: #dea584 !important; }
+.file-icon-swift { color: #ffac45 !important; }
+.file-icon-sh { color: #89e051 !important; }
+.file-icon-bash { color: #89e051 !important; }
+.file-icon-sql { color: #e38c00 !important; }
+.file-icon-vue { color: #41b883 !important; }
+.file-icon-agi { color: #f1e05a !important; }
+.file-icon-yaml { color: #cb171e !important; }
+.file-icon-yml { color: #cb171e !important; }
+.file-icon-toml { color: #9c4221 !important; }
+.file-icon-ini { color: #d1dbe0 !important; }
+.file-icon-env { color: #ecd53f !important; }
+.file-icon-gitignore { color: #f14e32 !important; }
+.file-icon-dockerfile { color: #384d54 !important; }
+
+/* Directory item - editable state */
+#directoryList .item.editing,
+#directoryList .subitem.editing {
+    background-color: rgba(255, 255, 255, 0.1);
+}
+
+#directoryList .item input.rename-input,
+#directoryList .subitem input.rename-input {
+    background: #1e1e1e;
+    border: 1px solid #007acc;
+    color: white;
+    padding: 2px 4px;
+    margin-left: 4px;
+    font-size: 12px;
+    outline: none;
+    width: 120px;
+}
+
+/* Folder drag and drop hover states */
+#directoryList .folderObjectWrapper.drag-over > .item,
+#directoryList .folderObjectWrapper.drag-over > .subitem,
+#directoryExplorer.drag-over {
+    background-color: rgba(0, 122, 204, 0.3) !important;
+    border: 1px dashed #007acc;
+}
+
+/* Context menu for folder tree */
+#folderContextMenu,
+#tabContextMenu {
+    background-color: #252526;
+    border: 1px solid #454545;
+    box-shadow: 2px 3px 13px 0px rgba(0,0,0,0.39);
+    position: fixed;
+    z-index: 1000;
+    min-width: 180px;
+    display: none;
+    color: white;
+    font-size: 13px;
+}
+
+#folderContextMenu .item,
+#tabContextMenu .item {
+    padding: 6px 12px;
+    cursor: pointer;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+
+#folderContextMenu .item:hover,
+#tabContextMenu .item:hover {
+    background-color: #094771;
+}
+
+#folderContextMenu .item .shortcut,
+#tabContextMenu .item .shortcut {
+    color: #808080;
+    font-size: 11px;
+    margin-left: 20px;
+}
+
+#folderContextMenu .divider,
+#tabContextMenu .divider {
+    border-bottom: 1px solid #454545;
+    margin: 4px 0;
+}
+
+#folderContextMenu .item.disabled,
+#tabContextMenu .item.disabled {
+    color: #6e6e6e;
+    cursor: not-allowed;
+}
+
+#folderContextMenu .item.disabled:hover,
+#tabContextMenu .item.disabled:hover {
+    background-color: transparent;
+}
+
+/* Tab drag indicator */
+.tabs .item.drag-over-left {
+    border-left: 2px solid #007acc !important;
+}
+
+.tabs .item.drag-over-right {
+    border-right: 2px solid #007acc !important;
+}
+
+/* Tab close animations */
+.tabs .item.closing {
+    animation: tabClose 0.2s ease-out forwards;
+}
+
+@keyframes tabClose {
+    from { opacity: 1; max-width: 200px; }
+    to { opacity: 0; max-width: 0; padding: 0; margin: 0; }
+}
+
+/* Open editors section styles */
+#openeditors .item {
+    position: relative;
+}
+
+#openeditors .item .closebtn {
+    opacity: 0;
+    transition: opacity 0.1s;
+}
+
+#openeditors .item:hover .closebtn {
+    opacity: 1;
+}
+
+/* Selected state for directory items */
+#directoryList .item.file-selected,
+#directoryList .subitem.file-selected {
+    background-color: rgba(0, 122, 204, 0.3);
+}
+

+ 315 - 0
src/web/Code Studio/backend/fileOps.agi

@@ -0,0 +1,315 @@
+/*
+    File Operations API for Code Studio
+    Author: Code Studio Team
+    
+    Supported operations:
+    - newFile: Create a new file
+    - newFolder: Create a new folder
+    - rename: Rename a file or folder
+    - move: Move a file or folder to a new location
+    - delete: Delete a file or folder
+    - listDir: List directory contents
+    - exists: Check if a file/folder exists
+*/
+
+if (!requirelib("filelib")){
+    sendJSONResp(JSON.stringify({
+        error: "Unable to load filelib"
+    }));
+}else{
+    // Get operation type
+    var operation = opr;
+    
+    if (operation == "newFile"){
+        // Create a new file
+        // Required params: filepath (full path including filename)
+        if (typeof filepath == "undefined"){
+            sendJSONResp(JSON.stringify({
+                error: "Missing filepath parameter"
+            }));
+        }else{
+            var content = "";
+            if (typeof initialContent !== "undefined"){
+                content = initialContent;
+            }
+            if (filelib.writeFile(filepath, content)){
+                sendJSONResp(JSON.stringify({
+                    success: true,
+                    filepath: filepath
+                }));
+            }else{
+                sendJSONResp(JSON.stringify({
+                    error: "Failed to create file: " + filepath
+                }));
+            }
+        }
+    }else if (operation == "newFolder"){
+        // Create a new folder
+        // Required params: folderpath
+        if (typeof folderpath == "undefined"){
+            sendJSONResp(JSON.stringify({
+                error: "Missing folderpath parameter"
+            }));
+        }else{
+            if (filelib.mkdir(folderpath)){
+                sendJSONResp(JSON.stringify({
+                    success: true,
+                    folderpath: folderpath
+                }));
+            }else{
+                sendJSONResp(JSON.stringify({
+                    error: "Failed to create folder: " + folderpath
+                }));
+            }
+        }
+    }else if (operation == "rename"){
+        // Rename a file or folder
+        // Required params: oldpath, newpath
+        if (typeof oldpath == "undefined" || typeof newpath == "undefined"){
+            sendJSONResp(JSON.stringify({
+                error: "Missing oldpath or newpath parameter"
+            }));
+        }else{
+            // Check if source exists
+            if (!filelib.fileExists(oldpath)){
+                sendJSONResp(JSON.stringify({
+                    error: "Source file/folder does not exist: " + oldpath
+                }));
+            }else{
+                if (filelib.isDir(oldpath)){
+                    // It's a directory - need to create new, copy contents, delete old
+                    // For now, we'll use mkdir for the new directory
+                    // This is a simplified approach - a full implementation would need recursive copy
+                    if (filelib.mkdir(newpath)){
+                        // Copy all files from old directory to new
+                        var files = filelib.readdir(oldpath);
+                        var success = true;
+                        if (files && files.length > 0){
+                            for (var i = 0; i < files.length; i++){
+                                var file = files[i];
+                                var srcPath = oldpath + "/" + file.Filename;
+                                var dstPath = newpath + "/" + file.Filename;
+                                
+                                if (file.IsDir){
+                                    // Nested directory - for simplicity we skip deep nesting
+                                    // A full implementation would use recursion
+                                    filelib.mkdir(dstPath);
+                                } else {
+                                    var content = filelib.readFile(srcPath);
+                                    if (content !== false){
+                                        filelib.writeFile(dstPath, content);
+                                        filelib.deleteFile(srcPath);
+                                    }
+                                }
+                            }
+                        }
+                        // Try to delete old folder (will only work if empty now)
+                        filelib.deleteFile(oldpath);
+                        sendJSONResp(JSON.stringify({
+                            success: true,
+                            oldpath: oldpath,
+                            newpath: newpath
+                        }));
+                    }else{
+                        sendJSONResp(JSON.stringify({
+                            error: "Failed to create new folder"
+                        }));
+                    }
+                }else{
+                    // It's a file - read content, write to new location, delete old
+                    var content = filelib.readFile(oldpath);
+                    if (content === false){
+                        sendJSONResp(JSON.stringify({
+                            error: "Failed to read source file"
+                        }));
+                    }else{
+                        if (filelib.writeFile(newpath, content)){
+                            if (filelib.deleteFile(oldpath)){
+                                sendJSONResp(JSON.stringify({
+                                    success: true,
+                                    oldpath: oldpath,
+                                    newpath: newpath
+                                }));
+                            }else{
+                                sendJSONResp(JSON.stringify({
+                                    error: "Failed to delete original file after rename"
+                                }));
+                            }
+                        }else{
+                            sendJSONResp(JSON.stringify({
+                                error: "Failed to write to new location"
+                            }));
+                        }
+                    }
+                }
+            }
+        }
+    }else if (operation == "move"){
+        // Move a file to a new folder
+        // Required params: sourcepath, destfolder
+        if (typeof sourcepath == "undefined" || typeof destfolder == "undefined"){
+            sendJSONResp(JSON.stringify({
+                error: "Missing sourcepath or destfolder parameter"
+            }));
+        }else{
+            // Get the filename from sourcepath
+            var parts = sourcepath.split("/");
+            var filename = parts.pop();
+            var destpath = destfolder;
+            if (!destpath.endsWith("/")){
+                destpath += "/";
+            }
+            destpath += filename;
+            
+            // Check if source exists
+            if (!filelib.fileExists(sourcepath)){
+                sendJSONResp(JSON.stringify({
+                    error: "Source file does not exist: " + sourcepath
+                }));
+            }else{
+                if (filelib.isDir(sourcepath)){
+                    // Moving a directory - create at destination
+                    if (filelib.mkdir(destpath)){
+                        // Copy all files from source to destination
+                        var files = filelib.readdir(sourcepath);
+                        if (files && files.length > 0){
+                            for (var i = 0; i < files.length; i++){
+                                var file = files[i];
+                                var srcPath = sourcepath + "/" + file.Filename;
+                                var dstPath = destpath + "/" + file.Filename;
+                                
+                                if (!file.IsDir){
+                                    var content = filelib.readFile(srcPath);
+                                    if (content !== false){
+                                        filelib.writeFile(dstPath, content);
+                                        filelib.deleteFile(srcPath);
+                                    }
+                                }
+                            }
+                        }
+                        // Try to delete old folder
+                        filelib.deleteFile(sourcepath);
+                        sendJSONResp(JSON.stringify({
+                            success: true,
+                            sourcepath: sourcepath,
+                            destpath: destpath
+                        }));
+                    }else{
+                        sendJSONResp(JSON.stringify({
+                            error: "Failed to create destination folder"
+                        }));
+                    }
+                }else{
+                    // Moving a file
+                    var content = filelib.readFile(sourcepath);
+                    if (content === false){
+                        sendJSONResp(JSON.stringify({
+                            error: "Failed to read source file"
+                        }));
+                    }else{
+                        if (filelib.writeFile(destpath, content)){
+                            if (filelib.deleteFile(sourcepath)){
+                                sendJSONResp(JSON.stringify({
+                                    success: true,
+                                    sourcepath: sourcepath,
+                                    destpath: destpath
+                                }));
+                            }else{
+                                sendJSONResp(JSON.stringify({
+                                    error: "Failed to delete original file after move"
+                                }));
+                            }
+                        }else{
+                            sendJSONResp(JSON.stringify({
+                                error: "Failed to write to destination"
+                            }));
+                        }
+                    }
+                }
+            }
+        }
+    }else if (operation == "delete"){
+        // Delete a file or folder
+        // Required params: filepath
+        if (typeof filepath == "undefined"){
+            sendJSONResp(JSON.stringify({
+                error: "Missing filepath parameter"
+            }));
+        }else{
+            if (!filelib.fileExists(filepath)){
+                sendJSONResp(JSON.stringify({
+                    error: "File/folder does not exist: " + filepath
+                }));
+            }else{
+                // Recursively delete contents before deleting the target
+                // (filelib.deleteFile ignores errors on non-empty dirs)
+                function deleteRecursive(path){
+                    if (filelib.isDir(path)){
+                        var items = filelib.readdir(path);
+                        if (items && items.length > 0){
+                            for (var i = 0; i < items.length; i++){
+                                deleteRecursive(path + "/" + items[i].Filename);
+                            }
+                        }
+                    }
+                    filelib.deleteFile(path);
+                }
+                deleteRecursive(filepath);
+                if (!filelib.fileExists(filepath)){
+                    sendJSONResp(JSON.stringify({
+                        success: true,
+                        filepath: filepath
+                    }));
+                }else{
+                    sendJSONResp(JSON.stringify({
+                        error: "Failed to delete: " + filepath
+                    }));
+                }
+            }
+        }
+    }else if (operation == "listDir"){
+        // List directory contents
+        // Required params: dirpath
+        if (typeof dirpath == "undefined"){
+            sendJSONResp(JSON.stringify({
+                error: "Missing dirpath parameter"
+            }));
+        }else{
+            var files = filelib.readdir(dirpath);
+            if (files === false){
+                sendJSONResp(JSON.stringify({
+                    error: "Failed to list directory: " + dirpath
+                }));
+            }else{
+                sendJSONResp(JSON.stringify({
+                    success: true,
+                    files: files
+                }));
+            }
+        }
+    }else if (operation == "exists"){
+        // Check if file/folder exists
+        // Required params: filepath
+        if (typeof filepath == "undefined"){
+            sendJSONResp(JSON.stringify({
+                error: "Missing filepath parameter"
+            }));
+        }else{
+            var exists = filelib.fileExists(filepath);
+            var isDir = false;
+            if (exists){
+                isDir = filelib.isDir(filepath);
+            }
+            sendJSONResp(JSON.stringify({
+                success: true,
+                exists: exists,
+                isDir: isDir,
+                filepath: filepath
+            }));
+        }
+    }else{
+        sendJSONResp(JSON.stringify({
+            error: "Unknown operation: " + operation
+        }));
+    }
+}

+ 7 - 1
src/web/Code Studio/backend/read.agi

@@ -11,6 +11,12 @@ if (!requirelib("filelib")){
     }));
 }else{
     var fileContent = filelib.readFile(file);
-    sendResp(fileContent);
+    if (fileContent === false){
+        sendJSONResp(JSON.stringify({
+            error: "File not found"
+        }));
+    }else{
+        sendResp(fileContent);
+    }
 }
 

+ 973 - 197
src/web/Code Studio/index.html

@@ -22,13 +22,6 @@
                padding:1px;
                background-color:#292929;
            }
-           #terminals{
-               position: fixed;
-               right: 0px;
-               bottom: 24px;
-               width: calc(100% - 240px);
-               
-           }
        </style>
     </head>
     <body>
@@ -49,7 +42,7 @@
                 
             </div>
             <div class="title item" onclick="toggleDirectoryExplorer(this);"><i class="caret down icon"></i><span id="openFolderTitle">NO FOLDER</span></div>
-            <div class="subgroup" id="directoryExplorer">
+            <div class="subgroup" id="directoryExplorer" oncontextmenu="showDirectoryExplorerContextMenu(event);">
               
             </div>
         </div>
@@ -62,28 +55,7 @@
                 <div class="editor" editorUID="mainEditor" onclick="focusThisEditor(this);"></div>
                 <div class="editorcover" editorUID="mainEditor" onclick="focusThisEditor(this);"></div>
             </div>
-            <div id="ca2" class="codeBoard splitMode right" style="display:none;">
-                <div class="tabs" editorUID="subEditor" ondrop="TabDrop(event)" ondragover="allowDrop(event)">
 
-                </div>
-                <div class="editor"  editorUID="subEditor" onclick="focusThisEditor(this);"></div>
-                <div class="editorcover" editorUID="subEditor" onclick="focusThisEditor(this);"></div>
-            </div>
-            <div id="terminals" style="display:none;">
-                <div class="menubar"> 
-                    <p>TERMINAL</p> 
-                    <div style="position: absolute; right: 12px;">
-                        <i style="cursor: pointer;" onclick="restartTerminal();" class="refresh icon"></i>
-                        <i style="cursor: pointer;" onclick="closeTerminal();" class="remove icon"></i>
-                    </div>
-                    
-                </div>
-                <div class="terminalWindow focused">
-                    <iframe id="initTerminal" src="../wstty/">
-                        
-                    </iframe>
-                </div>
-            </div>
         </div>
         <div class="bottommenu">
             <div class="item"><i class="exchange icon"></i><span id="pingStatus"></span></div>
@@ -99,6 +71,46 @@
             
         </div>
 
+        <!-- Folder Tree Context Menu -->
+        <div id="folderContextMenu">
+            <div class="item" onclick="newFileInFolder();">
+                <span><i class="file outline icon"></i> New File</span>
+                <span class="shortcut">Ctrl+N</span>
+            </div>
+            <div class="item" onclick="newFolderInFolder();">
+                <span><i class="folder outline icon"></i> New Folder</span>
+            </div>
+            <div class="divider"></div>
+            <div class="item" onclick="renameSelectedItem();">
+                <span><i class="edit icon"></i> Rename</span>
+                <span class="shortcut">F2</span>
+            </div>
+            <div class="item" onclick="deleteSelectedItem();">
+                <span><i class="trash icon"></i> Delete</span>
+                <span class="shortcut">Del</span>
+            </div>
+            <div class="divider"></div>
+            <div class="item" onclick="copyFilePath();">
+                <span><i class="copy icon"></i> Copy Path</span>
+            </div>
+            <div class="item" onclick="revealInFileManager();">
+                <span><i class="folder open icon"></i> Reveal in File Manager</span>
+            </div>
+        </div>
+
+        <!-- Tab Context Menu -->
+        <div id="tabContextMenu" class="contextMenu" style="display:none;">
+            <div class="item" onclick="closeCurrentTab();">Close</div>
+            <div class="item" onclick="closeOtherTabs();">Close Others</div>
+            <div class="item" onclick="closeTabsToTheRight();">Close to the Right</div>
+            <div class="item" onclick="closeTabsToTheLeft();">Close to the Left</div>
+            <div class="divider"></div>
+            <div class="item" onclick="closeAllTabsInEditor();">Close All</div>
+            <div class="divider"></div>
+            <div class="item" onclick="copyFilePathFromTab();">Copy Path</div>
+            <div class="item" onclick="revealTabInFileManager();">Reveal in File Manager</div>
+        </div>
+
         <!-- Licnese information -->
         <div id="licenseInfo" class="ui modal">
             <i class="close icon"></i>
@@ -162,19 +174,18 @@
         <script>
             //Environment Paramters
             let editors = [];                       //List of opened editor
-            let splitMode = false;                   //Split the screen into two half
             let focusedEditor = "mainEditor";       //The editor currently is focused
             let loadedModels = [];                  //Loaded models. Prevent duplicate loading problem
             let focusedFileInfo = [];
             let files = ao_module_loadInputFiles(); //Files imported from file system
 
-            //Terminal realted
-            let terminalStartingEndpoint = "../wstty/";
+            //Folder tree state
+            let currentProjectFolder = null;        //The currently opened project folder path
+            let selectedFolderItem = null;          //Currently selected item in folder tree (for context menu)
+            let selectedTabInfo = null;             //Currently selected tab (for tab context menu)
 
             //Initiation functions  
             initEditor($("#ca1").find(".editor")[0], "mainEditor", files, restoreEditorState);
-            initEditor($("#ca2").find(".editor")[0],"subEditor");
-
             //Load ready page into the editorCover
             $(".editorcover").load("home.html");
            
@@ -220,10 +231,6 @@
                         })
                     }
 
-                    //Open terminal if it is enabled previously
-                    if (oldState.terminal !== undefined && oldState.terminal == true){
-                        openTerminal();
-                    }
                 }
             }
             
@@ -233,26 +240,7 @@
                     setFontSize(fontsize);
                 });
 
-                getStorage("splitmode",function(splitSetting){
-                    if (splitSetting.error !== undefined){
-                        return;
-                    }
-                    splitSetting = splitSetting.trim();
-                     if (splitSetting == "true"){
-                        splitMode = true;
-                        $("#ca1").attr("class","codeBoard splitMode left")
-                        $("#ca2").show();
-                     }else{
-                        splitMode = false;
-                        $("#ca1").attr("class","codeBoard")
-                        $("#ca2").hide();
-                     }
-
-                    //Update all editor's size
-                    for (var i =0; i < editors.length; i++){
-                        editors[i].editor.layout();
-                    }
-                });
+
             }
         
             //Check if it is run under vdi mode. If not, establish checkauth ping to keep session alive
@@ -298,7 +286,7 @@
                             targetEditor = editors[i];
                         }
                     }
-                    if (typeof targetEditor === undefined){
+                    if (targetEditor === undefined){
                         console.log("Error when loading tab");
                         return;
                     }
@@ -313,6 +301,9 @@
                     });
                 });
 
+                //Bind tab context menu
+                bindTabContextMenu();
+
                 $("#openeditors .item").off("click").on("click",function(evt){
                     //Update UI to match selected tab
                     $("#openeditors .item.selected").removeClass('selected');
@@ -327,7 +318,7 @@
                             targetEditorObject = editors[i];
                         }
                     }
-                    if (typeof targetEditorObject === undefined){
+                    if (targetEditorObject === undefined){
                         console.log("Error when loading tab");
                         return;
                     }
@@ -390,7 +381,12 @@
 
                 //Remove the tab objects from DOM
                 removeTabFromDOMWithUUID(tabUUID);
-                
+
+                //Update window hash to reflect closed tab
+                var currentState = getHashObject();
+                currentState["files"] = getOpenedFiles();
+                writeHashObject(currentState);
+
                 if (targetEditorObject.tabs.length > 0){
                     //Switch to the first tab of the editor
                     var newFocusedTabUUID = targetEditorObject.tabs[0].tabUUID;
@@ -405,6 +401,7 @@
 
             function getFocusedTabInfo(){
                 var focusedEditorObject = getFocusedEditorObject();
+                if (!focusedEditorObject) return undefined;
                 var focusedTabUUID = focusedEditorObject.currentTabUUID;
                 for (var i =0; i < focusedEditorObject.tabs.length; i++){
                     var thistab = focusedEditorObject.tabs[i];
@@ -522,6 +519,8 @@
                     <div class="divider"></div>
                     <div class="item" onclick="closeFileCurrentlyFocused();">Close File</div>
                     <div class="item" onclick="closeAllFiles();">Close All Files</div>
+                    <div class="item" onclick="closeTabsRight();">Close Tabs to the Right</div>
+                    <div class="item" onclick="closeTabsLeft();">Close Tabs to the Left</div>
                     `);
 
                     if (ao_module_virtualDesktop){
@@ -551,9 +550,7 @@
                     <div class="item" onclick="showClipboardReminders(5);">Replace
                         <div class="tips">Ctrl + H</div>
                     </div>
-                    <div class="divider"></div>
-                    <div class="item" onclick="toggleSplit(true);">Split WorkSpace</div>
-                    <div class="item" onclick="toggleSplit(false);">Merge WorkSpace</div>
+
                    
                     `);
                 }else if (target == "view"){
@@ -570,9 +567,6 @@
                         <div class="item" onclick="openFileInFileManager(); hideContextMenu();">Reveal in File Manager 
                             <div class="tips"><i class="folder outline icon"></i></div>
                         </div>
-                        <div class="item" onclick="openTerminal(); hideContextMenu();">Open Terminal
-                            <div class="tips"><i class="code icon"></i></div>
-                        </div>
                         <div class="divider"></div>
                         <div class="item" onclick="downloadFile(); hideContextMenu();">Download
                             <div class="tips"><i class="download icon"></i></div>    
@@ -639,6 +633,51 @@
                 hideContextMenu();
             }
 
+            // Close tabs to the right of currently focused tab (from main menu)
+            function closeTabsRight(){
+                var focusedEditorObj = getFocusedEditorObject();
+                if (!focusedEditorObj) return;
+                
+                var currentTabUUID = focusedEditorObj.currentTabUUID;
+                var foundSelected = false;
+                var tabsToClose = [];
+                
+                focusedEditorObj.tabs.forEach(tab => {
+                    if (foundSelected){
+                        tabsToClose.push(tab.tabUUID);
+                    }
+                    if (tab.tabUUID == currentTabUUID){
+                        foundSelected = true;
+                    }
+                });
+
+                tabsToClose.forEach(tabUUID => {
+                    closeTabWithUUIDAndEditorID(tabUUID, focusedEditorObj.uuid);
+                });
+                hideContextMenu();
+            }
+
+            // Close tabs to the left of currently focused tab (from main menu)
+            function closeTabsLeft(){
+                var focusedEditorObj = getFocusedEditorObject();
+                if (!focusedEditorObj) return;
+                
+                var currentTabUUID = focusedEditorObj.currentTabUUID;
+                var tabsToClose = [];
+                
+                for (var i = 0; i < focusedEditorObj.tabs.length; i++){
+                    if (focusedEditorObj.tabs[i].tabUUID == currentTabUUID){
+                        break;
+                    }
+                    tabsToClose.push(focusedEditorObj.tabs[i].tabUUID);
+                }
+
+                tabsToClose.forEach(tabUUID => {
+                    closeTabWithUUIDAndEditorID(tabUUID, focusedEditorObj.uuid);
+                });
+                hideContextMenu();
+            }
+
           
             function saveAllFiles(){
                 //Store the editor object before saveall
@@ -672,43 +711,20 @@
                 hideContextMenu();
             }
 
-            function toggleSplit(useSplitMode){
-                if (useSplitMode){
-                    $("#ca1").attr("class","codeBoard splitMode left")
-                    $("#ca2").show();
-                    setStorage("splitmode","true");
-                }else{
-                    $("#ca1").attr("class","codeBoard")
-                    $("#ca2").hide();
-                    setStorage("splitmode","false");
-
-                    //Move all tabs from subEditor to main editor
-                    var mainEditor = getEditor("mainEditor");
-                    var subEditor = getEditor("subEditor");
-                    subEditor.tabs.forEach(tab => {
-                        var filepath = tab.filepath;
-                        //Open tab in main editor
-                        openFile(filepath, false, mainEditor);
-
-                        //Close tab in sub editor
-                        closeTabWithUUIDAndEditorID(tab.tabUUID, "subEditor");
-                    });
-                }
-
-
-                //Update all editor's size
-                for (var i =0; i < editors.length; i++){
-                    editors[i].editor.layout();
-                }
-                
-                hideContextMenu();
-            }
-            
             //Hide all the context menus
             function hideContextMenu(){
                 $("#contextMenu").hide();
             }
 
+            function buildErrorCoverHTML(filepath, tabUUID, editorUUID){
+                return `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;user-select:none;">
+                    <i class="times circle icon" style="font-size:3em;color:#e57373;margin-bottom:16px;"></i>
+                    <div style="font-size:1.1em;color:#e57373;margin-bottom:8px;">File or folder no longer exists</div>
+                    <div style="font-size:0.85em;color:#888;margin-bottom:20px;word-break:break-all;max-width:400px;text-align:center;">${filepath}</div>
+                    <div class="ui button" onclick="closeTabWithUUIDAndEditorID(${tabUUID},'${editorUUID}')">Close File</div>
+                </div>`;
+            }
+
             //open a file with a new editor by the given filepath
             function openFile(filepath, updateHashStatus=true, overrideEditor=undefined){
                 //Get the focused editor
@@ -744,6 +760,26 @@
                 ao_module_agirun("Code Studio/backend/read.agi",{
                     file: filepath
                 }, function(filecontent){
+                    // File not found — create an error tab instead of a Monaco model
+                    if (typeof filecontent === "object" && filecontent.error){
+                        let targetOpeningEditor = targetEditorGroup;
+                        var editorUUID = targetOpeningEditor.uuid;
+                        var targetTabMenu = targetOpeningEditor.tabsMenu;
+                        var tabUUID = new Date().getTime();
+                        var filename = filepath.split("/").pop();
+                        $(targetTabMenu).find(".item.selected").removeClass("selected");
+                        $(targetTabMenu).append(`<div class="item selected fileTab" title="${filepath}" draggable="true" ondragstart="fileTabDrag(event)" uuid="${tabUUID}" editorUUID="${editorUUID}" filepath="${filepath}"><i class="times circle red icon"></i> <span class="tabFilename">${filename}</span> <div class="closebtn"><i class="remove icon"></i></div></div>`);
+                        $("#openeditors .item").removeClass("selected");
+                        $("#openeditors").append(`<div class="item selected" uuid="${tabUUID}" editorUUID="${editorUUID}"><div class="closebtn" style="display:inline-block;"><i class="remove icon"></i></div> <i class="times circle red icon"></i> <span class="tabFilename">${filename}</span> </div>`);
+                        targetOpeningEditor.currentTabUUID = tabUUID;
+                        targetOpeningEditor.tabs.push({ filename: filename, filepath: filepath, tabUUID: tabUUID, saveHash: 0, error: true });
+                        var ca = getCodeAreaFromEditorUUID(editorUUID);
+                        $(ca).find(".editorcover").html(buildErrorCoverHTML(filepath, tabUUID, editorUUID)).show();
+                        bindTabItemEvents();
+                        focusTabWithUUID(tabUUID);
+                        return;
+                    }
+
                     //Get the target Opening Editor
                     let targetOpeningEditor = targetEditorGroup;
                     
@@ -769,11 +805,11 @@
                     var tabUUID = new Date().getTime();
                     var filename = filepath.split("/").pop();
                     $(targetTabMenu).find(".item.selected").removeClass("selected");
-                    $(targetTabMenu).append(`<div class="item selected fileTab" title="${filepath}" draggable="true" ondragstart="fileTabDrag(event)" uuid="${tabUUID}"  editorUUID="${editorUUID}" filepath="${filepath}"><i class="code file icon"></i> ${filename} <div class="closebtn"><i class="remove icon"></i></i></div></div>`);
+                    $(targetTabMenu).append(`<div class="item selected fileTab" title="${filepath}" draggable="true" ondragstart="fileTabDrag(event)" uuid="${tabUUID}"  editorUUID="${editorUUID}" filepath="${filepath}"><i class="code file icon"></i> <span class="tabFilename">${filename}</span> <div class="closebtn"><i class="remove icon"></i></i></div></div>`);
 
                     //Add this tab into the open editor list
                     $("#openeditors .item").removeClass("selected");
-                    $("#openeditors").append(`<div class="item selected" uuid="${tabUUID}" editorUUID="${editorUUID}"><div class="closebtn" style="display:inline-block;"><i class="remove icon"></i></div> <i class="code file icon"></i> ${filename} </div>`);
+                    $("#openeditors").append(`<div class="item selected" uuid="${tabUUID}" editorUUID="${editorUUID}"><div class="closebtn" style="display:inline-block;"><i class="remove icon"></i></div> <i class="code file icon"></i> <span class="tabFilename">${filename}</span> </div>`);
 
                     //Update the current tag information
                     targetOpeningEditor.currentTabUUID = tabUUID
@@ -870,12 +906,14 @@
 
             function openProjectFolder(filedata, rewriteHash=true){
                 for (var i=0; i < filedata.length; i++){
-                    folderpath = filedata[i].filepath;
-                    foldername = filedata[i].filename;
+                    let folderpath = filedata[i].filepath;
+                    let foldername = filedata[i].filename;
+                    currentProjectFolder = folderpath;
+
                     //Load this as the folder 
                     $("#openFolderTitle").text(foldername.toUpperCase());
                     $("#directoryExplorer").html("");
-                    $.get("../system/file_system/listDir?dir=" + encodeURIComponent(folderpath),function(data){
+                    $.get("../system/file_system/listDir?dir=" + folderpath,function(data){
                         var folders = [];
                         var files = [];
                         if (data === null){
@@ -887,19 +925,28 @@
                             var icon = ao_module_utils.getIconFromExt(ext);
                             var filepath = data[i].Filepath;
                             if (data[i].IsDir){
-                                icon = "folder";
+                                var folderIconClass = getFolderIconClass(filename);
                                 folders.push(`
-                                <div class="folderObjectWrapper">
-                                    <div class="item" title="${filepath}" style="overflow:hidden;" onclick="listfolder(this);">
+                                <div class="folderObjectWrapper" data-path="${filepath}" data-name="${filename}" data-isdir="true"
+                                     ondragover="folderDragOver(event)" ondragleave="folderDragLeave(event)" ondrop="folderDrop(event)">
+                                    <div class="item" title="${filepath}" style="overflow:hidden;" 
+                                         onclick="listfolder(this);" 
+                                         oncontextmenu="showFolderContextMenu(event, this);">
                                         <div class="showmore" style="display:inline-block;"><i class="caret right icon"></i></div>
-                                        <i class="${icon} icon"></i> ${filename} 
+                                        <i class="folder icon folder-icon ${folderIconClass}"></i> ${filename} 
                                     </div>
                                 </div>
                                 `);
                             }else{
+                                var fileIconClass = getFileIconClass(ext);
                                 files.push(`
-                                <div class="item" title="${filepath}" draggable="true" ondragstart="directoryFileDrag(event)" filepath="${filepath}" filename="${filename}"  style="overflow:hidden;" onclick="openFileViaDirectoryExplorer(this, event);">
-                                    <i class="${icon} icon"></i> ${filename} 
+                                <div class="item" title="${filepath}" data-path="${filepath}" data-name="${filename}" data-isdir="false"
+                                     draggable="true" ondragstart="directoryFileDrag(event)" 
+                                     filepath="${filepath}" filename="${filename}" 
+                                     style="overflow:hidden;" 
+                                     onclick="openFileViaDirectoryExplorer(this, event);"
+                                     oncontextmenu="showFolderContextMenu(event, this);">
+                                    <i class="${icon} icon ${fileIconClass}"></i> ${filename} 
                                 </div>
                                 `);
                             }
@@ -909,15 +956,45 @@
                         $("#directoryExplorer").append(files.join(""));
                     });
 
-                    if (rewriteHash){
+                    if (!ao_module_virtualDesktop && rewriteHash){
                         var currentState = getHashObject();
-                        currentState["folder"] = foldername;
+                        currentState["folder"] = folderpath;
                         writeHashObject(currentState);
                     }
 
                 }
             }
 
+            // Get colored folder icon class based on folder name
+            function getFolderIconClass(folderName){
+                var name = folderName.toLowerCase();
+                var specialFolders = ['src', 'lib', 'test', 'tests', 'spec', 'config', 'public', 'static', 
+                    'assets', 'images', 'img', 'css', 'styles', 'js', 'scripts', 'components', 
+                    'views', 'pages', 'templates', 'models', 'controllers', 'api', 'routes', 
+                    'middleware', 'utils', 'helpers', 'vendor', 'node_modules', 'build', 
+                    'dist', 'out', 'bin', 'docs', 'documentation', 'backend', 'frontend', 
+                    'server', 'client', 'database', 'db', 'migrations', 'seeds'];
+                
+                if (specialFolders.includes(name)){
+                    return name;
+                }
+                return '';
+            }
+
+            // Get file icon class based on extension
+            function getFileIconClass(ext){
+                var coloredExts = ['js', 'ts', 'jsx', 'tsx', 'html', 'htm', 'css', 'scss', 'sass', 
+                    'less', 'json', 'xml', 'md', 'py', 'go', 'java', 'c', 'cpp', 'cs', 'php', 
+                    'rb', 'rs', 'swift', 'sh', 'bash', 'sql', 'vue', 'agi', 'yaml', 'yml', 
+                    'toml', 'ini', 'env', 'gitignore', 'dockerfile'];
+                
+                ext = ext.toLowerCase();
+                if (coloredExts.includes(ext)){
+                    return 'file-icon-' + ext;
+                }
+                return '';
+            }
+
             function listfolder(folder){
                 var folderPath = $(folder).attr("title");
 
@@ -939,19 +1016,28 @@
                             var icon = ao_module_utils.getIconFromExt(ext);
                             var filepath = data[i].Filepath;
                             if (data[i].IsDir){
-                                icon = "folder";
+                                var folderIconClass = getFolderIconClass(filename);
                                 folders.push(`
-                                <div class="folderObjectWrapper">
-                                    <div class="subitem" title="${filepath}" style="overflow:hidden;" onclick="listfolder(this);">
+                                <div class="folderObjectWrapper" data-path="${filepath}" data-name="${filename}" data-isdir="true"
+                                     ondragover="folderDragOver(event)" ondragleave="folderDragLeave(event)" ondrop="folderDrop(event)">
+                                    <div class="subitem" title="${filepath}" style="overflow:hidden;" 
+                                         onclick="listfolder(this);"
+                                         oncontextmenu="showFolderContextMenu(event, this);">
                                         <div class="showmore" style="display:inline-block;"><i class="caret right icon"></i></div>
-                                        <i class="${icon} icon"></i> ${filename} 
+                                        <i class="folder icon folder-icon ${folderIconClass}"></i> ${filename} 
                                     </div>
                                 </div>
                                 `);
                             }else{
+                                var fileIconClass = getFileIconClass(ext);
                                 files.push(`
-                                <div class="subitem" title="${filepath}" style="overflow:hidden;" onclick="openFileViaDirectoryExplorer(this,event);">
-                                    <i class="${icon} icon"></i> ${filename} 
+                                <div class="subitem" title="${filepath}" data-path="${filepath}" data-name="${filename}" data-isdir="false"
+                                     draggable="true" ondragstart="directoryFileDrag(event)"
+                                     filepath="${filepath}" filename="${filename}"
+                                     style="overflow:hidden;" 
+                                     onclick="openFileViaDirectoryExplorer(this,event);"
+                                     oncontextmenu="showFolderContextMenu(event, this);">
+                                    <i class="${icon} icon ${fileIconClass}"></i> ${filename} 
                                 </div>
                                 `);
                             }
@@ -1012,6 +1098,29 @@
                         theme: 'vs-dark'
                     });
 
+                    //Detect content changes and mark tab as unsaved
+                    editor.onDidChangeModelContent(function() {
+                        var thisEditorObject;
+                        for (var i = 0; i < editors.length; i++) {
+                            if (editors[i].editor === editor) {
+                                thisEditorObject = editors[i];
+                                break;
+                            }
+                        }
+                        if (!thisEditorObject) return;
+                        var currentTabUUID = thisEditorObject.currentTabUUID;
+                        var currentTab;
+                        for (var i = 0; i < thisEditorObject.tabs.length; i++) {
+                            if (thisEditorObject.tabs[i].tabUUID == currentTabUUID) {
+                                currentTab = thisEditorObject.tabs[i];
+                                break;
+                            }
+                        }
+                        if (!currentTab) return;
+                        var isModified = editor.getValue().hashCode() !== currentTab.saveHash;
+                        markTabUnsaved(currentTabUUID, isModified);
+                    });
+
                     //Bind save event for this editor
                     editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, function() {
                         let thisEditor = editor;
@@ -1060,40 +1169,6 @@
               
             }
 
-            /*
-                Terminal Function Related
-
-            */
-
-            function terminalInit(){
-                $(".ui.dropdown").dropdown();
-            }
-
-            var codeAreaBottomOffset = 0;
-            function openTerminal(){
-                //Open WsTTY on the editor
-                codeAreaBottomOffset = 300;
-                $("#terminals").show();
-                updateCodeAreaSize();
-                hideContextMenu();
-                updateStatusHash("terminal",true);
-            }
-
-            function closeTerminal(){
-                codeAreaBottomOffset = 0;
-                $("#terminals").hide();
-                updateCodeAreaSize();
-                updateStatusHash("terminal",false);
-            }
-
-            function restartTerminal(){
-                $("#terminals").find(".terminalWindow.focused").find("iframe").attr("src","");
-                setTimeout(function(){
-                    $("#terminals").find(".terminalWindow.focused").find("iframe").attr("src",terminalStartingEndpoint);
-                }, 1000);
-                
-            }
-
             $(window).on("resize", function(data){
                 updateCodeAreaSize();
             });
@@ -1101,7 +1176,7 @@
             updateCodeAreaSize();
             function updateCodeAreaSize(){
                 $("#codeArea").css({
-                    "height":window.innerHeight - 52 - codeAreaBottomOffset
+                    "height":window.innerHeight - 52
                 });
 
                 editors.forEach(editor => {
@@ -1173,50 +1248,98 @@
                 editorObject.currentTabUUID = targettabUUID;
 
                 //Load the content from the given uuid tab
-                editor.setModel(models[targettabUUID]);
-                editor.restoreViewState(states[targettabUUID]);
-                editor.focus();
+                var targetTab = editorObject.tabs.find(t => t.tabUUID == targettabUUID);
+                var ca = getCodeAreaFromEditorUUID(editorObject.uuid);
+                if (targetTab && targetTab.error){
+                    $(ca).find(".editorcover").html(buildErrorCoverHTML(targetTab.filepath, targettabUUID, editorObject.uuid)).show();
+                } else {
+                    $(ca).find(".editorcover").hide();
+                    editor.setModel(models[targettabUUID]);
+                    editor.restoreViewState(states[targettabUUID]);
+                    editor.focus();
+                }
 
                 //Update the target file info
                 var tabInfo = getFocusedTabInfo();
-                updateFileStatusDisplay(tabInfo.filepath);
-
-                //Update title if nonVDI or update floatWindow title if under fdi
-                if (ao_module_virtualDesktop){
-                    ao_module_setWindowTitle("Code Studio - " + tabInfo.filename);
-                }else{
-                    document.title = "Code Studio - " + tabInfo.filename
+                if (tabInfo){
+                    updateFileStatusDisplay(tabInfo.filepath);
+                    if (ao_module_virtualDesktop){
+                        ao_module_setWindowTitle("Code Studio - " + tabInfo.filename);
+                    }else{
+                        document.title = "Code Studio - " + tabInfo.filename;
+                    }
                 }
 
                 //Focus this tab
                 focusTabWithUUID(targettabUUID);
             }
 
-            //Show clipboard reminders
+            //Show clipboard reminders (or execute the action directly if the browser supports it)
             function showClipboardReminders(rt){
-                var reminderText = "This function is keyboard shortcut only.";
-                var iconClass = "keyboard icon";
-                if (rt == 1){
-                    reminderText = "Use the keyboard shortcut Ctrl + X to cut text from the editor.";
-                    iconClass = "cut icon";
-                }else if (rt == 2){
-                    reminderText = "Use the keyboard shortcut Ctrl + C for copying text from the editor";
-                    iconClass = "copy icon";
-                }else if (rt == 3){
-                    reminderText = "Use the keyboard shortcut Ctrl + V for pasting to editor.";
-                    iconClass = "paste icon";
-                }else if (rt == 4){
-                    reminderText = "Press Ctrl + F on the editor to start searching.";
-                    iconClass = "search icon";
-                }else if (rt == 5){
-                    reminderText = "Press Ctrl + H on the editor to start replacing text.";
-                    iconClass = "sync alternate icon";
+                var editorObj = getFocusedEditorObject();
+                var monacoEditor = editorObj ? editorObj.editor : null;
+
+                // Map rt -> { Monaco action id, fallback icon, fallback hint }
+                var actionMap = {
+                    1: { action: "editor.action.clipboardCutAction",     icon: "cut icon",            hint: "Use the keyboard shortcut Ctrl + X to cut text from the editor." },
+                    2: { action: "editor.action.clipboardCopyAction",    icon: "copy icon",           hint: "Use the keyboard shortcut Ctrl + C for copying text from the editor." },
+                    3: { action: "editor.action.clipboardPasteAction",   icon: "paste icon",          hint: "Use the keyboard shortcut Ctrl + V for pasting to editor." },
+                    4: { action: "actions.find",                         icon: "search icon",         hint: "Press Ctrl + F on the editor to start searching." },
+                    5: { action: "editor.action.startFindReplaceAction", icon: "sync alternate icon", hint: "Press Ctrl + H on the editor to start replacing text." }
+                };
+
+                var entry = actionMap[rt];
+                if (!entry) return;
+
+                function showFallback(){
+                    $("#clipboardReminderText").text(entry.hint);
+                    $("#clipboardReminderIcon").attr("class", entry.icon);
+                    $("#clipboardReminder").modal("show");
+                    hideContextMenu();
                 }
 
-                $("#clipboardReminderText").text(reminderText);
-                $("#clipboardReminderIcon").attr("class",iconClass)
-                $('#clipboardReminder').modal('show');
-                hideContextMenu();
+                if (!monacoEditor){
+                    showFallback();
+                    return;
+                }
+
+                // Paste (rt==3): requires clipboard-read permission — use Clipboard API when available
+                if (rt == 3){
+                    if (navigator.clipboard && navigator.clipboard.readText){
+                        navigator.clipboard.readText().then(function(text){
+                            monacoEditor.focus();
+                            var sel = monacoEditor.getSelection();
+                            monacoEditor.executeEdits("paste", [{
+                                range: new monaco.Range(sel.startLineNumber, sel.startColumn, sel.endLineNumber, sel.endColumn),
+                                text: text,
+                                forceMoveMarkers: true
+                            }]);
+                        }).catch(function(){
+                            // Permission denied or clipboard empty — fall back to reminder
+                            showFallback();
+                        });
+                        hideContextMenu();
+                    } else {
+                        // No Clipboard API — try Monaco trigger, fall back to reminder on error
+                        try {
+                            monacoEditor.focus();
+                            monacoEditor.trigger("keyboard", entry.action, null);
+                            hideContextMenu();
+                        } catch(e){
+                            showFallback();
+                        }
+                    }
+                    return;
+                }
+
+                // Cut, Copy, Find, Replace — trigger Monaco action directly; no permission needed
+                try {
+                    monacoEditor.focus();
+                    monacoEditor.trigger("keyboard", entry.action, null);
+                    hideContextMenu();
+                } catch(e){
+                    showFallback();
+                }
             }
 
 
@@ -1316,12 +1439,23 @@
                             var thisTab = thisEditor.tabs[j];
                             if (thisTab.tabUUID == tabUUID){
                                 thisTab.saveHash = contextHash;
+                                markTabUnsaved(tabUUID, false);
                             }
                         }
                     }
                 });
             }
 
+            function markTabUnsaved(tabUUID, unsaved) {
+                $(".tabs .item, #openeditors .item").each(function() {
+                    if ($(this).attr("uuid") == tabUUID) {
+                        var nameSpan = $(this).find(".tabFilename");
+                        var currentName = nameSpan.text().replace(/^\* /, "");
+                        nameSpan.text(unsaved ? "* " + currentName : currentName);
+                    }
+                });
+            }
+
             function msgbox(icon, message){
                 $("#messageField").html(`<i class="${icon} icon"></i> ${message}`);
                 $("#messageField").stop().finish().hide().fadeIn('fast').delay(5000).fadeOut('fast');
@@ -1489,25 +1623,18 @@
 
             //Drag file from directory explorer to editor tabs
             function directoryFileDrag(ev){
-                var tabFilepath = $(ev.target).attr("filepath");
-                var tabFilename = $(ev.target).attr("filename");
+                var tabFilepath = $(ev.target).attr("filepath") || $(ev.target).data("path");
+                var tabFilename = $(ev.target).attr("filename") || $(ev.target).data("name");
                 var uuid = Date.now(); //Generate a new UUID for this
 
                 ev.dataTransfer.setData("uuid", uuid);
                 ev.dataTransfer.setData("filename", tabFilename);
                 ev.dataTransfer.setData("filepath", tabFilepath);
+                ev.dataTransfer.effectAllowed = "move";
             }
 
-            //Drag tab from one editor top to another
-            function fileTabDrag(ev) {
-                var tabUUID = $(ev.target).attr("uuid");
-                var tabFilepath = $(ev.target).attr("filepath");
-                var tabFilename = $(ev.target).text();
-                
-                ev.dataTransfer.setData("uuid", tabUUID);
-                ev.dataTransfer.setData("filename", tabFilename);
-                ev.dataTransfer.setData("filepath", tabFilepath);
-            }
+            //Drag tab from one editor to another - this is the original, to be removed
+            // Replaced by the enhanced fileTabDrag function below
 
             function TabDrop(ev) {
                 if ($(ev.target).hasClass("tabs") == false){
@@ -1518,6 +1645,7 @@
                 var tabUUID = ev.dataTransfer.getData("uuid");
                 var tabFilename = ev.dataTransfer.getData("filename");
                 var tabFilepath = ev.dataTransfer.getData("filepath");
+                var sourceEditorUUID = ev.dataTransfer.getData("sourceEditorUUID");
                 
                 if (tabFilename == "" || tabFilepath == ""){
                     return;
@@ -1530,11 +1658,659 @@
                     return
                 }
 
+                //Check if this is a tab being moved between editors
+                if (sourceEditorUUID && sourceEditorUUID !== targetEditor.uuid){
+                    //Close the tab from source editor first
+                    closeTabWithUUIDAndEditorID(tabUUID, sourceEditorUUID);
+                }
+
                 //Open the file in the given editor
                 openFile(tabFilepath, true, targetEditor);
                 console.log(targetEditor, tabUUID, tabFilename, tabFilepath);
             }
 
+            /*
+                =========================================
+                Folder Tree Context Menu Functions
+                =========================================
+            */
+
+            // Show folder context menu
+            function showFolderContextMenu(event, element){
+                event.preventDefault();
+                event.stopPropagation();
+                
+                // Store reference to clicked item
+                selectedFolderItem = {
+                    element: element,
+                    path: $(element).attr("title") || $(element).parent().data("path"),
+                    name: $(element).parent().data("name") || $(element).data("name"),
+                    isDir: $(element).parent().data("isdir") === true || $(element).data("isdir") === "true"
+                };
+
+                // Highlight selected item
+                $("#directoryList .item, #directoryList .subitem").removeClass("file-selected");
+                $(element).addClass("file-selected");
+
+                // Position and show context menu
+                var menu = $("#folderContextMenu");
+                menu.css({
+                    left: event.pageX,
+                    top: event.pageY
+                });
+                menu.show();
+
+                // Hide on click outside
+                $(document).one("click", function(){
+                    hideFolderContextMenu();
+                });
+            }
+
+            // Show context menu when right-clicking on empty area of directory explorer
+            function showDirectoryExplorerContextMenu(event){
+                // Only trigger if clicking directly on the directory explorer, not on items
+                if ($(event.target).attr("id") !== "directoryExplorer"){
+                    return;
+                }
+                
+                event.preventDefault();
+                event.stopPropagation();
+                
+                if (!currentProjectFolder){
+                    return; // No project folder open
+                }
+                
+                // Set selected folder item to project root
+                selectedFolderItem = {
+                    element: null,
+                    path: currentProjectFolder,
+                    name: $("#openFolderTitle").text(),
+                    isDir: true
+                };
+
+                // Position and show context menu
+                var menu = $("#folderContextMenu");
+                menu.css({
+                    left: event.pageX,
+                    top: event.pageY
+                });
+                menu.show();
+
+                $(document).one("click", function(){
+                    hideFolderContextMenu();
+                });
+            }
+
+            function hideFolderContextMenu(){
+                $("#folderContextMenu").hide();
+                $("#directoryList .item, #directoryList .subitem").removeClass("file-selected");
+            }
+
+            // New file in selected folder
+            function newFileInFolder(){
+                hideFolderContextMenu();
+                var targetPath = currentProjectFolder;
+                
+                if (selectedFolderItem){
+                    if (selectedFolderItem.isDir){
+                        targetPath = selectedFolderItem.path;
+                    } else {
+                        // Get parent folder of the file
+                        var parts = selectedFolderItem.path.split("/");
+                        parts.pop();
+                        targetPath = parts.join("/");
+                    }
+                }
+
+                var filename = prompt("Enter new file name:", "newfile.txt");
+                if (filename && filename.trim() !== ""){
+                    var filepath = targetPath;
+                    if (!filepath.endsWith("/")){
+                        filepath += "/";
+                    }
+                    filepath += filename.trim();
+
+                    // Create the file
+                    ao_module_agirun("Code Studio/backend/fileOps.agi", {
+                        opr: "newFile",
+                        filepath: filepath
+                    }, function(data){
+                        var result = data;
+                        if (result.error){
+                            alert("Failed to create file: " + result.error);
+                        } else {
+                            // Refresh the folder tree
+                            refreshFolderTree();
+                            // Open the new file
+                            openFile(filepath);
+                            msgbox("checkmark", "File created");
+                        }
+                    });
+                }
+            }
+
+            // New folder in selected folder
+            function newFolderInFolder(){
+                hideFolderContextMenu();
+                var targetPath = currentProjectFolder;
+                
+                if (selectedFolderItem){
+                    if (selectedFolderItem.isDir){
+                        targetPath = selectedFolderItem.path;
+                    } else {
+                        var parts = selectedFolderItem.path.split("/");
+                        parts.pop();
+                        targetPath = parts.join("/");
+                    }
+                }
+
+                var foldername = prompt("Enter new folder name:", "NewFolder");
+                if (foldername && foldername.trim() !== ""){
+                    var folderpath = targetPath;
+                    if (!folderpath.endsWith("/")){
+                        folderpath += "/";
+                    }
+                    folderpath += foldername.trim();
+
+                    ao_module_agirun("Code Studio/backend/fileOps.agi", {
+                        opr: "newFolder",
+                        folderpath: folderpath
+                    }, function(data){
+                        var result = data;
+                        if (result.error){
+                            alert("Failed to create folder: " + result.error);
+                        } else {
+                            refreshFolderTree();
+                            msgbox("checkmark", "Folder created");
+                        }
+                    });
+                }
+            }
+
+            // Rename selected item
+            function renameSelectedItem(){
+                hideFolderContextMenu();
+                if (!selectedFolderItem) return;
+
+                var newName = prompt("Enter new name:", selectedFolderItem.name);
+                if (newName && newName.trim() !== "" && newName !== selectedFolderItem.name){
+                    var oldPath = selectedFolderItem.path;
+                    var pathParts = oldPath.split("/");
+                    pathParts.pop();
+                    var newPath = pathParts.join("/") + "/" + newName.trim();
+
+                    ao_module_agirun("Code Studio/backend/fileOps.agi", {
+                        opr: "rename",
+                        oldpath: oldPath,
+                        newpath: newPath
+                    }, function(data){
+                        var result = data;
+                        if (result.error){
+                            alert("Failed to rename: " + result.error);
+                        } else {
+                            refreshFolderTree();
+                            
+                            // Update any open tabs with the old path
+                            updateOpenTabsAfterRename(oldPath, newPath);
+                            msgbox("checkmark", "Renamed successfully");
+                        }
+                    });
+                }
+            }
+
+            // Delete selected item
+            function deleteSelectedItem(){
+                hideFolderContextMenu();
+                if (!selectedFolderItem) return;
+
+                var itemType = selectedFolderItem.isDir ? "folder" : "file";
+                if (confirm("Are you sure you want to delete this " + itemType + "?\n" + selectedFolderItem.name)){
+                    ao_module_agirun("Code Studio/backend/fileOps.agi", {
+                        opr: "delete",
+                        filepath: selectedFolderItem.path
+                    }, function(data){
+                        var result = data;
+                        if (result.error){
+                            alert("Failed to delete: " + result.error);
+                        } else {
+                            refreshFolderTree();
+                            
+                            // Close any open tabs for the deleted file
+                            closeTabsWithPath(selectedFolderItem.path);
+                            msgbox("checkmark", "Deleted successfully");
+                        }
+                    });
+                }
+            }
+
+            // Copy file path to clipboard
+            function copyFilePath(){
+                hideFolderContextMenu();
+                if (!selectedFolderItem) return;
+
+                copyToClipboard(selectedFolderItem.path);
+                msgbox("copy", "Path copied to clipboard");
+            }
+
+            // Reveal in file manager
+            function revealInFileManager(){
+                hideFolderContextMenu();
+                if (!selectedFolderItem) return;
+
+                var pathParts = selectedFolderItem.path.split("/");
+                var filename = pathParts.pop();
+                var dirname = pathParts.join("/");
+
+                ao_module_openPath(dirname, filename);
+            }
+
+            // Refresh the folder tree
+            function refreshFolderTree(){
+                if (currentProjectFolder){
+                    openProjectFolder([{
+                        filename: $("#openFolderTitle").text(),
+                        filepath: currentProjectFolder
+                    }], false);
+                }
+            }
+
+            // Update open tabs after rename
+            function updateOpenTabsAfterRename(oldPath, newPath){
+                editors.forEach(editor => {
+                    editor.tabs.forEach(tab => {
+                        if (tab.filepath === oldPath || tab.filepath.startsWith(oldPath + "/")){
+                            tab.filepath = tab.filepath.replace(oldPath, newPath);
+                            tab.filename = tab.filepath.split("/").pop();
+                        }
+                    });
+                });
+                // Refresh tab display
+                renderAllTabs();
+            }
+
+            // Close tabs with specific path
+            function closeTabsWithPath(path){
+                editors.forEach(editor => {
+                    var tabsToClose = [];
+                    editor.tabs.forEach(tab => {
+                        if (tab.filepath === path || tab.filepath.startsWith(path + "/")){
+                            tabsToClose.push(tab.tabUUID);
+                        }
+                    });
+                    tabsToClose.forEach(tabUUID => {
+                        closeTabWithUUIDAndEditorID(tabUUID, editor.uuid);
+                    });
+                });
+            }
+
+            // Render all tabs (refresh display)
+            function renderAllTabs(){
+                editors.forEach(editor => {
+                    var tabsMenu = $(editor.tabsMenu);
+                    tabsMenu.find(".item").each(function(){
+                        var uuid = $(this).attr("uuid");
+                        var tab = editor.tabs.find(t => t.tabUUID == uuid);
+                        if (tab){
+                            $(this).attr("filepath", tab.filepath);
+                            $(this).attr("title", tab.filepath);
+                            $(this).find(".tabFilename").text(tab.filename);
+                        }
+                    });
+                });
+                // Also update open editors sidebar
+                editors.forEach(editor => {
+                    editor.tabs.forEach(tab => {
+                        $("#openeditors .item[uuid='" + tab.tabUUID + "'] .tabFilename").text(tab.filename);
+                    });
+                });
+            }
+
+            // Copy to clipboard helper
+            function copyToClipboard(text){
+                if (navigator.clipboard){
+                    navigator.clipboard.writeText(text);
+                } else {
+                    var textarea = document.createElement("textarea");
+                    textarea.value = text;
+                    document.body.appendChild(textarea);
+                    textarea.select();
+                    document.execCommand("copy");
+                    document.body.removeChild(textarea);
+                }
+            }
+
+            /*
+                =========================================
+                Folder Drag and Drop Functions
+                =========================================
+            */
+
+            function folderDragOver(event){
+                event.preventDefault();
+                event.stopPropagation();
+                $(event.currentTarget).addClass("drag-over");
+            }
+
+            function folderDragLeave(event){
+                event.preventDefault();
+                event.stopPropagation();
+                $(event.currentTarget).removeClass("drag-over");
+            }
+
+            function folderDrop(event){
+                event.preventDefault();
+                event.stopPropagation();
+                $(event.currentTarget).removeClass("drag-over");
+
+                var sourcePath = event.dataTransfer.getData("filepath");
+                var sourceFilename = event.dataTransfer.getData("filename");
+                var destFolder = $(event.currentTarget).data("path");
+
+                if (!sourcePath || !destFolder){
+                    return;
+                }
+
+                // Don't allow dropping into the same folder
+                var sourceDir = sourcePath.split("/");
+                sourceDir.pop();
+                sourceDir = sourceDir.join("/");
+                
+                if (sourceDir === destFolder){
+                    return;
+                }
+
+                // Move the file
+                ao_module_agirun("Code Studio/backend/fileOps.agi", {
+                    opr: "move",
+                    sourcepath: sourcePath,
+                    destfolder: destFolder
+                }, function(data){
+                    var result = data;
+                    if (result.error){
+                        alert("Failed to move: " + result.error);
+                    } else {
+                        refreshFolderTree();
+                        
+                        // Update any open tabs
+                        var newPath = destFolder;
+                        if (!newPath.endsWith("/")){
+                            newPath += "/";
+                        }
+                        newPath += sourceFilename;
+                        updateOpenTabsAfterRename(sourcePath, newPath);
+                        msgbox("checkmark", "Moved successfully");
+                    }
+                });
+            }
+
+            /*
+                =========================================
+                Tab Context Menu Functions
+                =========================================
+            */
+
+            // Add context menu to tabs
+            function bindTabContextMenu(){
+                $(".tabs .item").off("contextmenu").on("contextmenu", function(event){
+                    event.preventDefault();
+                    event.stopPropagation();
+                    
+                    var tabUUID = $(this).attr("uuid");
+                    var editorUUID = $(this).attr("editorUUID");
+                    var filepath = $(this).attr("filepath");
+                    
+                    selectedTabInfo = {
+                        tabUUID: tabUUID,
+                        editorUUID: editorUUID,
+                        filepath: filepath,
+                        element: this
+                    };
+
+                    // Position and show tab context menu
+                    var menu = $("#tabContextMenu");
+                    menu.css({
+                        left: event.pageX,
+                        top: event.pageY
+                    });
+                    menu.show();
+
+                    $(document).one("click", function(){
+                        hideTabContextMenu();
+                    });
+                });
+            }
+
+            function hideTabContextMenu(){
+                $("#tabContextMenu").hide();
+                selectedTabInfo = null;
+            }
+
+            function closeCurrentTab(){
+                var info = selectedTabInfo;
+                hideTabContextMenu();
+                if (!info) return;
+                closeTabWithUUIDAndEditorID(info.tabUUID, info.editorUUID);
+            }
+
+            function closeOtherTabs(){
+                var info = selectedTabInfo;
+                hideTabContextMenu();
+                if (!info) return;
+
+                var editor = getEditor(info.editorUUID);
+                if (!editor) return;
+
+                var tabsToClose = [];
+                editor.tabs.forEach(tab => {
+                    if (tab.tabUUID != info.tabUUID){
+                        tabsToClose.push(tab.tabUUID);
+                    }
+                });
+
+                tabsToClose.forEach(tabUUID => {
+                    closeTabWithUUIDAndEditorID(tabUUID, info.editorUUID);
+                });
+                // Restore focus to the right-clicked tab
+                changeTab(editor, info.tabUUID);
+            }
+
+            function closeTabsToTheRight(){
+                var info = selectedTabInfo;
+                hideTabContextMenu();
+                if (!info) return;
+
+                var editor = getEditor(info.editorUUID);
+                if (!editor) return;
+
+                var foundSelected = false;
+                var tabsToClose = [];
+                
+                editor.tabs.forEach(tab => {
+                    if (foundSelected){
+                        tabsToClose.push(tab.tabUUID);
+                    }
+                    if (tab.tabUUID == info.tabUUID){
+                        foundSelected = true;
+                    }
+                });
+
+                tabsToClose.forEach(tabUUID => {
+                    closeTabWithUUIDAndEditorID(tabUUID, info.editorUUID);
+                });
+                // Restore focus to the right-clicked tab
+                changeTab(editor, info.tabUUID);
+            }
+
+            function closeTabsToTheLeft(){
+                var info = selectedTabInfo;
+                hideTabContextMenu();
+                if (!info) return;
+
+                var editor = getEditor(info.editorUUID);
+                if (!editor) return;
+
+                var tabsToClose = [];
+                
+                for (var i = 0; i < editor.tabs.length; i++){
+                    if (editor.tabs[i].tabUUID == info.tabUUID){
+                        break;
+                    }
+                    tabsToClose.push(editor.tabs[i].tabUUID);
+                }
+
+                tabsToClose.forEach(tabUUID => {
+                    closeTabWithUUIDAndEditorID(tabUUID, info.editorUUID);
+                });
+                // Restore focus to the right-clicked tab
+                changeTab(editor, info.tabUUID);
+            }
+
+            function closeAllTabsInEditor(){
+                var info = selectedTabInfo;
+                hideTabContextMenu();
+                if (!info) return;
+
+                var editor = getEditor(info.editorUUID);
+                if (!editor) return;
+
+                var tabsToClose = editor.tabs.map(tab => tab.tabUUID);
+                tabsToClose.forEach(tabUUID => {
+                    closeTabWithUUIDAndEditorID(tabUUID, info.editorUUID);
+                });
+            }
+
+            function copyFilePathFromTab(){
+                var info = selectedTabInfo;
+                hideTabContextMenu();
+                if (!info || !info.filepath) return;
+
+                copyToClipboard(info.filepath);
+                msgbox("copy", "Path copied to clipboard");
+            }
+
+            function revealTabInFileManager(){
+                var info = selectedTabInfo;
+                hideTabContextMenu();
+                if (!info || !info.filepath) return;
+
+                var pathParts = info.filepath.split("/");
+                var filename = pathParts.pop();
+                var dirname = pathParts.join("/");
+
+                ao_module_openPath(dirname, filename);
+            }
+
+            /*
+                =========================================
+                Enhanced Tab Drag Functions
+                =========================================
+            */
+
+            //Drag tab from one editor to another (enhanced version)
+            function fileTabDrag(ev) {
+                var tabUUID = $(ev.target).attr("uuid");
+                var tabFilepath = $(ev.target).attr("filepath");
+                var tabFilename = $(ev.target).text().trim();
+                var sourceEditorUUID = $(ev.target).attr("editorUUID");
+                
+                ev.dataTransfer.setData("uuid", tabUUID);
+                ev.dataTransfer.setData("filename", tabFilename);
+                ev.dataTransfer.setData("filepath", tabFilepath);
+                ev.dataTransfer.setData("sourceEditorUUID", sourceEditorUUID);
+                ev.dataTransfer.effectAllowed = "move";
+            }
+
+            /*
+                =========================================
+                Enhanced Close Functions
+                =========================================
+            */
+
+            // Close all files with confirmation for unsaved
+            function closeAllFilesWithConfirm(){
+                var hasUnsavedChanges = false;
+                
+                editors.forEach(editor => {
+                    editor.tabs.forEach(tab => {
+                        var currentModel = editor.model[tab.tabUUID];
+                        if (currentModel){
+                            var currentContent = currentModel.getValue();
+                            if (currentContent.hashCode() !== tab.saveHash){
+                                hasUnsavedChanges = true;
+                            }
+                        }
+                    });
+                });
+
+                if (hasUnsavedChanges){
+                    if (!confirm("Some files have unsaved changes. Close anyway?")){
+                        return;
+                    }
+                }
+
+                closeAllFiles();
+            }
+
+            /*
+                =========================================
+                Global Event Handlers
+                =========================================
+            */
+
+            // Hide context menus when clicking elsewhere
+            $(document).on("click", function(event){
+                if (!$(event.target).closest("#folderContextMenu").length){
+                    hideFolderContextMenu();
+                }
+                if (!$(event.target).closest("#tabContextMenu").length){
+                    hideTabContextMenu();
+                }
+            });
+
+            // Keyboard shortcuts for folder tree
+            $(document).on("keydown", function(event){
+                // F2 for rename
+                if (event.key === "F2" && selectedFolderItem){
+                    renameSelectedItem();
+                }
+                // Delete key
+                if (event.key === "Delete" && selectedFolderItem){
+                    deleteSelectedItem();
+                }
+            });
+
+            // Directory explorer drop zone for moving files to root
+            $("#directoryExplorer").attr("ondragover", "allowDrop(event)");
+            $("#directoryExplorer").attr("ondrop", "directoryExplorerDrop(event)");
+
+            function directoryExplorerDrop(event){
+                event.preventDefault();
+                event.stopPropagation();
+                $("#directoryExplorer").removeClass("drag-over");
+
+                var sourcePath = event.dataTransfer.getData("filepath");
+                var sourceFilename = event.dataTransfer.getData("filename");
+
+                if (!sourcePath || !currentProjectFolder){
+                    return;
+                }
+
+                // Move to project root
+                ao_module_agirun("Code Studio/backend/fileOps.agi", {
+                    opr: "move",
+                    sourcepath: sourcePath,
+                    destfolder: currentProjectFolder
+                }, function(data){
+                    var result = data;
+                    if (result.error){
+                        alert("Failed to move: " + result.error);
+                    } else {
+                        refreshFolderTree();
+                        msgbox("checkmark", "Moved to project root");
+                    }
+                });
+            }
+
         </script>
     </body>
 </html>