diff --git a/src/components/Editors/Text/TextTab.ts b/src/components/Editors/Text/TextTab.ts index 13b4a395d..ddbc9c530 100644 --- a/src/components/Editors/Text/TextTab.ts +++ b/src/components/Editors/Text/TextTab.ts @@ -59,6 +59,20 @@ export class TextTab extends FileTab { app.project.tabActionProvider.addTabActions(this) }) } + + static from(fileTab: FileTab) { + const tab = new TextTab( + fileTab.tabSystem, + fileTab.getFileHandle(), + fileTab.readOnlyMode + ) + + tab.isTemporary = fileTab.isTemporary + tab.setReadOnly(fileTab.readOnlyMode) + + return tab + } + async getFile() { if (!this.editorModel || this.editorModel.isDisposed()) return await super.getFile() @@ -271,7 +285,10 @@ export class TextTab extends FileTab { setReadOnly(val: TReadOnlyMode) { this.readOnlyMode = val - this.editorInstance?.updateOptions({ readOnly: val !== 'off' }) + + // Only update options immediately if Monaco is already loaded + if (this.parent.hasFired) + this.editorInstance.updateOptions({ readOnly: val !== 'off' }) } async paste() { diff --git a/src/components/Editors/TreeEditor/Tab.ts b/src/components/Editors/TreeEditor/Tab.ts index 0b6fce259..897afb8d8 100644 --- a/src/components/Editors/TreeEditor/Tab.ts +++ b/src/components/Editors/TreeEditor/Tab.ts @@ -48,6 +48,18 @@ export class TreeTab extends FileTab { app.project.tabActionProvider.addTabActions(this) }) } + static from(fileTab: FileTab) { + const tab = new TreeTab( + fileTab.tabSystem, + fileTab.getFileHandle(), + fileTab.readOnlyMode + ) + + tab.isTemporary = fileTab.isTemporary + tab.setReadOnly(fileTab.readOnlyMode) + + return tab + } get app() { return this.parent.app diff --git a/src/components/TabSystem/TabSystem.ts b/src/components/TabSystem/TabSystem.ts index 1bd847572..bd83d4e99 100644 --- a/src/components/TabSystem/TabSystem.ts +++ b/src/components/TabSystem/TabSystem.ts @@ -171,6 +171,25 @@ export class TabSystem extends MonacoHolder { return tab } + + async replaceCurrent(newTab: Tab) { + const currentTab = this.selectedTab + if (!currentTab) return + + currentTab.onDeactivate() + currentTab.onDestroy() + + const tabIndex = this.tabs.value.findIndex( + (current) => current === currentTab + ) + if (tabIndex === -1) throw new Error('Tab not found') + + this.tabs.value.splice(tabIndex, 1, newTab) + + if (!newTab.hasFired) await newTab.fired + newTab.select() + } + async close(tab = this.selectedTab, checkUnsaved = true) { if (!tab) return false diff --git a/src/components/Toolbar/Category/view.ts b/src/components/Toolbar/Category/view.ts index 9b191d32a..c0d6d56ef 100644 --- a/src/components/Toolbar/Category/view.ts +++ b/src/components/Toolbar/Category/view.ts @@ -5,6 +5,8 @@ import { ViewCompilerOutput } from '../../UIElements/DirectoryViewer/ContextMenu import { Divider } from '../Divider' import { platform } from '/@/utils/os' import { fullScreenAction } from '../../TabSystem/TabContextMenu/Fullscreen' +import { TextTab } from '../../Editors/Text/TextTab' +import { TreeTab } from '../../Editors/TreeEditor/Tab' export function setupViewCategory(app: App) { const view = new ToolbarCategory('mdi-eye-outline', 'toolbar.view.name') @@ -144,6 +146,27 @@ export function setupViewCategory(app: App) { }, }) ) + view.addItem( + app.actionManager.create({ + icon: 'mdi-pencil-outline', + name: 'actions.switchEditorMode.name', + description: 'actions.switchEditorMode.description', + onTrigger: async () => { + const currentTab = app.tabSystem?.selectedTab + if ( + !(currentTab instanceof TextTab) && + !(currentTab instanceof TreeTab) + ) + return + + const newTab = + currentTab instanceof TextTab + ? TreeTab.from(currentTab) + : TextTab.from(currentTab) + currentTab.tabSystem.replaceCurrent(newTab) + }, + }) + ) App.toolbar.addCategory(view) }